diff options
Diffstat (limited to 'src/main/java/org/apache/commons/math3/ode/nonstiff')
57 files changed, 12401 insertions, 0 deletions
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsBashforthFieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsBashforthFieldIntegrator.java new file mode 100644 index 0000000..bec3343 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsBashforthFieldIntegrator.java @@ -0,0 +1,354 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.Field; +import org.apache.commons.math3.RealFieldElement; +import org.apache.commons.math3.exception.DimensionMismatchException; +import org.apache.commons.math3.exception.MaxCountExceededException; +import org.apache.commons.math3.exception.NoBracketingException; +import org.apache.commons.math3.exception.NumberIsTooSmallException; +import org.apache.commons.math3.linear.Array2DRowFieldMatrix; +import org.apache.commons.math3.linear.FieldMatrix; +import org.apache.commons.math3.ode.FieldExpandableODE; +import org.apache.commons.math3.ode.FieldODEState; +import org.apache.commons.math3.ode.FieldODEStateAndDerivative; +import org.apache.commons.math3.util.MathArrays; + + +/** + * This class implements explicit Adams-Bashforth integrators for Ordinary + * Differential Equations. + * + * <p>Adams-Bashforth methods (in fact due to Adams alone) are explicit + * multistep ODE solvers. This implementation is a variation of the classical + * one: it uses adaptive stepsize to implement error control, whereas + * classical implementations are fixed step size. The value of state vector + * at step n+1 is a simple combination of the value at step n and of the + * derivatives at steps n, n-1, n-2 ... Depending on the number k of previous + * steps one wants to use for computing the next value, different formulas + * are available:</p> + * <ul> + * <li>k = 1: y<sub>n+1</sub> = y<sub>n</sub> + h y'<sub>n</sub></li> + * <li>k = 2: y<sub>n+1</sub> = y<sub>n</sub> + h (3y'<sub>n</sub>-y'<sub>n-1</sub>)/2</li> + * <li>k = 3: y<sub>n+1</sub> = y<sub>n</sub> + h (23y'<sub>n</sub>-16y'<sub>n-1</sub>+5y'<sub>n-2</sub>)/12</li> + * <li>k = 4: y<sub>n+1</sub> = y<sub>n</sub> + h (55y'<sub>n</sub>-59y'<sub>n-1</sub>+37y'<sub>n-2</sub>-9y'<sub>n-3</sub>)/24</li> + * <li>...</li> + * </ul> + * + * <p>A k-steps Adams-Bashforth method is of order k.</p> + * + * <h3>Implementation details</h3> + * + * <p>We define scaled derivatives s<sub>i</sub>(n) at step n as: + * <pre> + * s<sub>1</sub>(n) = h y'<sub>n</sub> for first derivative + * s<sub>2</sub>(n) = h<sup>2</sup>/2 y''<sub>n</sub> for second derivative + * s<sub>3</sub>(n) = h<sup>3</sup>/6 y'''<sub>n</sub> for third derivative + * ... + * s<sub>k</sub>(n) = h<sup>k</sup>/k! y<sup>(k)</sup><sub>n</sub> for k<sup>th</sup> derivative + * </pre></p> + * + * <p>The definitions above use the classical representation with several previous first + * derivatives. Lets define + * <pre> + * q<sub>n</sub> = [ s<sub>1</sub>(n-1) s<sub>1</sub>(n-2) ... s<sub>1</sub>(n-(k-1)) ]<sup>T</sup> + * </pre> + * (we omit the k index in the notation for clarity). With these definitions, + * Adams-Bashforth methods can be written: + * <ul> + * <li>k = 1: y<sub>n+1</sub> = y<sub>n</sub> + s<sub>1</sub>(n)</li> + * <li>k = 2: y<sub>n+1</sub> = y<sub>n</sub> + 3/2 s<sub>1</sub>(n) + [ -1/2 ] q<sub>n</sub></li> + * <li>k = 3: y<sub>n+1</sub> = y<sub>n</sub> + 23/12 s<sub>1</sub>(n) + [ -16/12 5/12 ] q<sub>n</sub></li> + * <li>k = 4: y<sub>n+1</sub> = y<sub>n</sub> + 55/24 s<sub>1</sub>(n) + [ -59/24 37/24 -9/24 ] q<sub>n</sub></li> + * <li>...</li> + * </ul></p> + * + * <p>Instead of using the classical representation with first derivatives only (y<sub>n</sub>, + * s<sub>1</sub>(n) and q<sub>n</sub>), our implementation uses the Nordsieck vector with + * higher degrees scaled derivatives all taken at the same step (y<sub>n</sub>, s<sub>1</sub>(n) + * and r<sub>n</sub>) where r<sub>n</sub> is defined as: + * <pre> + * r<sub>n</sub> = [ s<sub>2</sub>(n), s<sub>3</sub>(n) ... s<sub>k</sub>(n) ]<sup>T</sup> + * </pre> + * (here again we omit the k index in the notation for clarity) + * </p> + * + * <p>Taylor series formulas show that for any index offset i, s<sub>1</sub>(n-i) can be + * computed from s<sub>1</sub>(n), s<sub>2</sub>(n) ... s<sub>k</sub>(n), the formula being exact + * for degree k polynomials. + * <pre> + * s<sub>1</sub>(n-i) = s<sub>1</sub>(n) + ∑<sub>j>0</sub> (j+1) (-i)<sup>j</sup> s<sub>j+1</sub>(n) + * </pre> + * The previous formula can be used with several values for i to compute the transform between + * classical representation and Nordsieck vector. The transform between r<sub>n</sub> + * and q<sub>n</sub> resulting from the Taylor series formulas above is: + * <pre> + * q<sub>n</sub> = s<sub>1</sub>(n) u + P r<sub>n</sub> + * </pre> + * where u is the [ 1 1 ... 1 ]<sup>T</sup> vector and P is the (k-1)×(k-1) matrix built + * with the (j+1) (-i)<sup>j</sup> terms with i being the row number starting from 1 and j being + * the column number starting from 1: + * <pre> + * [ -2 3 -4 5 ... ] + * [ -4 12 -32 80 ... ] + * P = [ -6 27 -108 405 ... ] + * [ -8 48 -256 1280 ... ] + * [ ... ] + * </pre></p> + * + * <p>Using the Nordsieck vector has several advantages: + * <ul> + * <li>it greatly simplifies step interpolation as the interpolator mainly applies + * Taylor series formulas,</li> + * <li>it simplifies step changes that occur when discrete events that truncate + * the step are triggered,</li> + * <li>it allows to extend the methods in order to support adaptive stepsize.</li> + * </ul></p> + * + * <p>The Nordsieck vector at step n+1 is computed from the Nordsieck vector at step n as follows: + * <ul> + * <li>y<sub>n+1</sub> = y<sub>n</sub> + s<sub>1</sub>(n) + u<sup>T</sup> r<sub>n</sub></li> + * <li>s<sub>1</sub>(n+1) = h f(t<sub>n+1</sub>, y<sub>n+1</sub>)</li> + * <li>r<sub>n+1</sub> = (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub></li> + * </ul> + * where A is a rows shifting matrix (the lower left part is an identity matrix): + * <pre> + * [ 0 0 ... 0 0 | 0 ] + * [ ---------------+---] + * [ 1 0 ... 0 0 | 0 ] + * A = [ 0 1 ... 0 0 | 0 ] + * [ ... | 0 ] + * [ 0 0 ... 1 0 | 0 ] + * [ 0 0 ... 0 1 | 0 ] + * </pre></p> + * + * <p>The P<sup>-1</sup>u vector and the P<sup>-1</sup> A P matrix do not depend on the state, + * they only depend on k and therefore are precomputed once for all.</p> + * + * @param <T> the type of the field elements + * @since 3.6 + */ +public class AdamsBashforthFieldIntegrator<T extends RealFieldElement<T>> extends AdamsFieldIntegrator<T> { + + /** Integrator method name. */ + private static final String METHOD_NAME = "Adams-Bashforth"; + + /** + * Build an Adams-Bashforth integrator with the given order and step control parameters. + * @param field field to which the time and state vector elements belong + * @param nSteps number of steps of the method excluding the one being computed + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + * @exception NumberIsTooSmallException if order is 1 or less + */ + public AdamsBashforthFieldIntegrator(final Field<T> field, final int nSteps, + final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) + throws NumberIsTooSmallException { + super(field, METHOD_NAME, nSteps, nSteps, minStep, maxStep, + scalAbsoluteTolerance, scalRelativeTolerance); + } + + /** + * Build an Adams-Bashforth integrator with the given order and step control parameters. + * @param field field to which the time and state vector elements belong + * @param nSteps number of steps of the method excluding the one being computed + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + * @exception IllegalArgumentException if order is 1 or less + */ + public AdamsBashforthFieldIntegrator(final Field<T> field, final int nSteps, + final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) + throws IllegalArgumentException { + super(field, METHOD_NAME, nSteps, nSteps, minStep, maxStep, + vecAbsoluteTolerance, vecRelativeTolerance); + } + + /** Estimate error. + * <p> + * Error is estimated by interpolating back to previous state using + * the state Taylor expansion and comparing to real previous state. + * </p> + * @param previousState state vector at step start + * @param predictedState predicted state vector at step end + * @param predictedScaled predicted value of the scaled derivatives at step end + * @param predictedNordsieck predicted value of the Nordsieck vector at step end + * @return estimated normalized local discretization error + */ + private T errorEstimation(final T[] previousState, + final T[] predictedState, + final T[] predictedScaled, + final FieldMatrix<T> predictedNordsieck) { + + T error = getField().getZero(); + for (int i = 0; i < mainSetDimension; ++i) { + final T yScale = predictedState[i].abs(); + final T tol = (vecAbsoluteTolerance == null) ? + yScale.multiply(scalRelativeTolerance).add(scalAbsoluteTolerance) : + yScale.multiply(vecRelativeTolerance[i]).add(vecAbsoluteTolerance[i]); + + // apply Taylor formula from high order to low order, + // for the sake of numerical accuracy + T variation = getField().getZero(); + int sign = predictedNordsieck.getRowDimension() % 2 == 0 ? -1 : 1; + for (int k = predictedNordsieck.getRowDimension() - 1; k >= 0; --k) { + variation = variation.add(predictedNordsieck.getEntry(k, i).multiply(sign)); + sign = -sign; + } + variation = variation.subtract(predictedScaled[i]); + + final T ratio = predictedState[i].subtract(previousState[i]).add(variation).divide(tol); + error = error.add(ratio.multiply(ratio)); + + } + + return error.divide(mainSetDimension).sqrt(); + + } + + /** {@inheritDoc} */ + @Override + public FieldODEStateAndDerivative<T> integrate(final FieldExpandableODE<T> equations, + final FieldODEState<T> initialState, + final T finalTime) + throws NumberIsTooSmallException, DimensionMismatchException, + MaxCountExceededException, NoBracketingException { + + sanityChecks(initialState, finalTime); + final T t0 = initialState.getTime(); + final T[] y = equations.getMapper().mapState(initialState); + setStepStart(initIntegration(equations, t0, y, finalTime)); + final boolean forward = finalTime.subtract(initialState.getTime()).getReal() > 0; + + // compute the initial Nordsieck vector using the configured starter integrator + start(equations, getStepStart(), finalTime); + + // reuse the step that was chosen by the starter integrator + FieldODEStateAndDerivative<T> stepStart = getStepStart(); + FieldODEStateAndDerivative<T> stepEnd = + AdamsFieldStepInterpolator.taylor(stepStart, + stepStart.getTime().add(getStepSize()), + getStepSize(), scaled, nordsieck); + + // main integration loop + setIsLastStep(false); + do { + + T[] predictedY = null; + final T[] predictedScaled = MathArrays.buildArray(getField(), y.length); + Array2DRowFieldMatrix<T> predictedNordsieck = null; + T error = getField().getZero().add(10); + while (error.subtract(1.0).getReal() >= 0.0) { + + // predict a first estimate of the state at step end + predictedY = stepEnd.getState(); + + // evaluate the derivative + final T[] yDot = computeDerivatives(stepEnd.getTime(), predictedY); + + // predict Nordsieck vector at step end + for (int j = 0; j < predictedScaled.length; ++j) { + predictedScaled[j] = getStepSize().multiply(yDot[j]); + } + predictedNordsieck = updateHighOrderDerivativesPhase1(nordsieck); + updateHighOrderDerivativesPhase2(scaled, predictedScaled, predictedNordsieck); + + // evaluate error + error = errorEstimation(y, predictedY, predictedScaled, predictedNordsieck); + + if (error.subtract(1.0).getReal() >= 0.0) { + // reject the step and attempt to reduce error by stepsize control + final T factor = computeStepGrowShrinkFactor(error); + rescale(filterStep(getStepSize().multiply(factor), forward, false)); + stepEnd = AdamsFieldStepInterpolator.taylor(getStepStart(), + getStepStart().getTime().add(getStepSize()), + getStepSize(), + scaled, + nordsieck); + + } + } + + // discrete events handling + setStepStart(acceptStep(new AdamsFieldStepInterpolator<T>(getStepSize(), stepEnd, + predictedScaled, predictedNordsieck, forward, + getStepStart(), stepEnd, + equations.getMapper()), + finalTime)); + scaled = predictedScaled; + nordsieck = predictedNordsieck; + + if (!isLastStep()) { + + System.arraycopy(predictedY, 0, y, 0, y.length); + + if (resetOccurred()) { + // some events handler has triggered changes that + // invalidate the derivatives, we need to restart from scratch + start(equations, getStepStart(), finalTime); + } + + // stepsize control for next step + final T factor = computeStepGrowShrinkFactor(error); + final T scaledH = getStepSize().multiply(factor); + final T nextT = getStepStart().getTime().add(scaledH); + final boolean nextIsLast = forward ? + nextT.subtract(finalTime).getReal() >= 0 : + nextT.subtract(finalTime).getReal() <= 0; + T hNew = filterStep(scaledH, forward, nextIsLast); + + final T filteredNextT = getStepStart().getTime().add(hNew); + final boolean filteredNextIsLast = forward ? + filteredNextT.subtract(finalTime).getReal() >= 0 : + filteredNextT.subtract(finalTime).getReal() <= 0; + if (filteredNextIsLast) { + hNew = finalTime.subtract(getStepStart().getTime()); + } + + rescale(hNew); + stepEnd = AdamsFieldStepInterpolator.taylor(getStepStart(), getStepStart().getTime().add(getStepSize()), + getStepSize(), scaled, nordsieck); + + } + + } while (!isLastStep()); + + final FieldODEStateAndDerivative<T> finalState = getStepStart(); + setStepStart(null); + setStepSize(null); + return finalState; + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsBashforthIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsBashforthIntegrator.java new file mode 100644 index 0000000..af4a435 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsBashforthIntegrator.java @@ -0,0 +1,362 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.exception.DimensionMismatchException; +import org.apache.commons.math3.exception.MaxCountExceededException; +import org.apache.commons.math3.exception.NoBracketingException; +import org.apache.commons.math3.exception.NumberIsTooSmallException; +import org.apache.commons.math3.linear.Array2DRowRealMatrix; +import org.apache.commons.math3.linear.RealMatrix; +import org.apache.commons.math3.ode.EquationsMapper; +import org.apache.commons.math3.ode.ExpandableStatefulODE; +import org.apache.commons.math3.ode.sampling.NordsieckStepInterpolator; +import org.apache.commons.math3.util.FastMath; + + +/** + * This class implements explicit Adams-Bashforth integrators for Ordinary + * Differential Equations. + * + * <p>Adams-Bashforth methods (in fact due to Adams alone) are explicit + * multistep ODE solvers. This implementation is a variation of the classical + * one: it uses adaptive stepsize to implement error control, whereas + * classical implementations are fixed step size. The value of state vector + * at step n+1 is a simple combination of the value at step n and of the + * derivatives at steps n, n-1, n-2 ... Depending on the number k of previous + * steps one wants to use for computing the next value, different formulas + * are available:</p> + * <ul> + * <li>k = 1: y<sub>n+1</sub> = y<sub>n</sub> + h y'<sub>n</sub></li> + * <li>k = 2: y<sub>n+1</sub> = y<sub>n</sub> + h (3y'<sub>n</sub>-y'<sub>n-1</sub>)/2</li> + * <li>k = 3: y<sub>n+1</sub> = y<sub>n</sub> + h (23y'<sub>n</sub>-16y'<sub>n-1</sub>+5y'<sub>n-2</sub>)/12</li> + * <li>k = 4: y<sub>n+1</sub> = y<sub>n</sub> + h (55y'<sub>n</sub>-59y'<sub>n-1</sub>+37y'<sub>n-2</sub>-9y'<sub>n-3</sub>)/24</li> + * <li>...</li> + * </ul> + * + * <p>A k-steps Adams-Bashforth method is of order k.</p> + * + * <h3>Implementation details</h3> + * + * <p>We define scaled derivatives s<sub>i</sub>(n) at step n as: + * <pre> + * s<sub>1</sub>(n) = h y'<sub>n</sub> for first derivative + * s<sub>2</sub>(n) = h<sup>2</sup>/2 y''<sub>n</sub> for second derivative + * s<sub>3</sub>(n) = h<sup>3</sup>/6 y'''<sub>n</sub> for third derivative + * ... + * s<sub>k</sub>(n) = h<sup>k</sup>/k! y<sup>(k)</sup><sub>n</sub> for k<sup>th</sup> derivative + * </pre></p> + * + * <p>The definitions above use the classical representation with several previous first + * derivatives. Lets define + * <pre> + * q<sub>n</sub> = [ s<sub>1</sub>(n-1) s<sub>1</sub>(n-2) ... s<sub>1</sub>(n-(k-1)) ]<sup>T</sup> + * </pre> + * (we omit the k index in the notation for clarity). With these definitions, + * Adams-Bashforth methods can be written: + * <ul> + * <li>k = 1: y<sub>n+1</sub> = y<sub>n</sub> + s<sub>1</sub>(n)</li> + * <li>k = 2: y<sub>n+1</sub> = y<sub>n</sub> + 3/2 s<sub>1</sub>(n) + [ -1/2 ] q<sub>n</sub></li> + * <li>k = 3: y<sub>n+1</sub> = y<sub>n</sub> + 23/12 s<sub>1</sub>(n) + [ -16/12 5/12 ] q<sub>n</sub></li> + * <li>k = 4: y<sub>n+1</sub> = y<sub>n</sub> + 55/24 s<sub>1</sub>(n) + [ -59/24 37/24 -9/24 ] q<sub>n</sub></li> + * <li>...</li> + * </ul></p> + * + * <p>Instead of using the classical representation with first derivatives only (y<sub>n</sub>, + * s<sub>1</sub>(n) and q<sub>n</sub>), our implementation uses the Nordsieck vector with + * higher degrees scaled derivatives all taken at the same step (y<sub>n</sub>, s<sub>1</sub>(n) + * and r<sub>n</sub>) where r<sub>n</sub> is defined as: + * <pre> + * r<sub>n</sub> = [ s<sub>2</sub>(n), s<sub>3</sub>(n) ... s<sub>k</sub>(n) ]<sup>T</sup> + * </pre> + * (here again we omit the k index in the notation for clarity) + * </p> + * + * <p>Taylor series formulas show that for any index offset i, s<sub>1</sub>(n-i) can be + * computed from s<sub>1</sub>(n), s<sub>2</sub>(n) ... s<sub>k</sub>(n), the formula being exact + * for degree k polynomials. + * <pre> + * s<sub>1</sub>(n-i) = s<sub>1</sub>(n) + ∑<sub>j>0</sub> (j+1) (-i)<sup>j</sup> s<sub>j+1</sub>(n) + * </pre> + * The previous formula can be used with several values for i to compute the transform between + * classical representation and Nordsieck vector. The transform between r<sub>n</sub> + * and q<sub>n</sub> resulting from the Taylor series formulas above is: + * <pre> + * q<sub>n</sub> = s<sub>1</sub>(n) u + P r<sub>n</sub> + * </pre> + * where u is the [ 1 1 ... 1 ]<sup>T</sup> vector and P is the (k-1)×(k-1) matrix built + * with the (j+1) (-i)<sup>j</sup> terms with i being the row number starting from 1 and j being + * the column number starting from 1: + * <pre> + * [ -2 3 -4 5 ... ] + * [ -4 12 -32 80 ... ] + * P = [ -6 27 -108 405 ... ] + * [ -8 48 -256 1280 ... ] + * [ ... ] + * </pre></p> + * + * <p>Using the Nordsieck vector has several advantages: + * <ul> + * <li>it greatly simplifies step interpolation as the interpolator mainly applies + * Taylor series formulas,</li> + * <li>it simplifies step changes that occur when discrete events that truncate + * the step are triggered,</li> + * <li>it allows to extend the methods in order to support adaptive stepsize.</li> + * </ul></p> + * + * <p>The Nordsieck vector at step n+1 is computed from the Nordsieck vector at step n as follows: + * <ul> + * <li>y<sub>n+1</sub> = y<sub>n</sub> + s<sub>1</sub>(n) + u<sup>T</sup> r<sub>n</sub></li> + * <li>s<sub>1</sub>(n+1) = h f(t<sub>n+1</sub>, y<sub>n+1</sub>)</li> + * <li>r<sub>n+1</sub> = (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub></li> + * </ul> + * where A is a rows shifting matrix (the lower left part is an identity matrix): + * <pre> + * [ 0 0 ... 0 0 | 0 ] + * [ ---------------+---] + * [ 1 0 ... 0 0 | 0 ] + * A = [ 0 1 ... 0 0 | 0 ] + * [ ... | 0 ] + * [ 0 0 ... 1 0 | 0 ] + * [ 0 0 ... 0 1 | 0 ] + * </pre></p> + * + * <p>The P<sup>-1</sup>u vector and the P<sup>-1</sup> A P matrix do not depend on the state, + * they only depend on k and therefore are precomputed once for all.</p> + * + * @since 2.0 + */ +public class AdamsBashforthIntegrator extends AdamsIntegrator { + + /** Integrator method name. */ + private static final String METHOD_NAME = "Adams-Bashforth"; + + /** + * Build an Adams-Bashforth integrator with the given order and step control parameters. + * @param nSteps number of steps of the method excluding the one being computed + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + * @exception NumberIsTooSmallException if order is 1 or less + */ + public AdamsBashforthIntegrator(final int nSteps, + final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) + throws NumberIsTooSmallException { + super(METHOD_NAME, nSteps, nSteps, minStep, maxStep, + scalAbsoluteTolerance, scalRelativeTolerance); + } + + /** + * Build an Adams-Bashforth integrator with the given order and step control parameters. + * @param nSteps number of steps of the method excluding the one being computed + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + * @exception IllegalArgumentException if order is 1 or less + */ + public AdamsBashforthIntegrator(final int nSteps, + final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) + throws IllegalArgumentException { + super(METHOD_NAME, nSteps, nSteps, minStep, maxStep, + vecAbsoluteTolerance, vecRelativeTolerance); + } + + /** Estimate error. + * <p> + * Error is estimated by interpolating back to previous state using + * the state Taylor expansion and comparing to real previous state. + * </p> + * @param previousState state vector at step start + * @param predictedState predicted state vector at step end + * @param predictedScaled predicted value of the scaled derivatives at step end + * @param predictedNordsieck predicted value of the Nordsieck vector at step end + * @return estimated normalized local discretization error + */ + private double errorEstimation(final double[] previousState, + final double[] predictedState, + final double[] predictedScaled, + final RealMatrix predictedNordsieck) { + + double error = 0; + for (int i = 0; i < mainSetDimension; ++i) { + final double yScale = FastMath.abs(predictedState[i]); + final double tol = (vecAbsoluteTolerance == null) ? + (scalAbsoluteTolerance + scalRelativeTolerance * yScale) : + (vecAbsoluteTolerance[i] + vecRelativeTolerance[i] * yScale); + + // apply Taylor formula from high order to low order, + // for the sake of numerical accuracy + double variation = 0; + int sign = predictedNordsieck.getRowDimension() % 2 == 0 ? -1 : 1; + for (int k = predictedNordsieck.getRowDimension() - 1; k >= 0; --k) { + variation += sign * predictedNordsieck.getEntry(k, i); + sign = -sign; + } + variation -= predictedScaled[i]; + + final double ratio = (predictedState[i] - previousState[i] + variation) / tol; + error += ratio * ratio; + + } + + return FastMath.sqrt(error / mainSetDimension); + + } + + /** {@inheritDoc} */ + @Override + public void integrate(final ExpandableStatefulODE equations, final double t) + throws NumberIsTooSmallException, DimensionMismatchException, + MaxCountExceededException, NoBracketingException { + + sanityChecks(equations, t); + setEquations(equations); + final boolean forward = t > equations.getTime(); + + // initialize working arrays + final double[] y = equations.getCompleteState(); + final double[] yDot = new double[y.length]; + + // set up an interpolator sharing the integrator arrays + final NordsieckStepInterpolator interpolator = new NordsieckStepInterpolator(); + interpolator.reinitialize(y, forward, + equations.getPrimaryMapper(), equations.getSecondaryMappers()); + + // set up integration control objects + initIntegration(equations.getTime(), y, t); + + // compute the initial Nordsieck vector using the configured starter integrator + start(equations.getTime(), y, t); + interpolator.reinitialize(stepStart, stepSize, scaled, nordsieck); + interpolator.storeTime(stepStart); + + // reuse the step that was chosen by the starter integrator + double hNew = stepSize; + interpolator.rescale(hNew); + + // main integration loop + isLastStep = false; + do { + + interpolator.shift(); + final double[] predictedY = new double[y.length]; + final double[] predictedScaled = new double[y.length]; + Array2DRowRealMatrix predictedNordsieck = null; + double error = 10; + while (error >= 1.0) { + + // predict a first estimate of the state at step end + final double stepEnd = stepStart + hNew; + interpolator.storeTime(stepEnd); + final ExpandableStatefulODE expandable = getExpandable(); + final EquationsMapper primary = expandable.getPrimaryMapper(); + primary.insertEquationData(interpolator.getInterpolatedState(), predictedY); + int index = 0; + for (final EquationsMapper secondary : expandable.getSecondaryMappers()) { + secondary.insertEquationData(interpolator.getInterpolatedSecondaryState(index), predictedY); + ++index; + } + + // evaluate the derivative + computeDerivatives(stepEnd, predictedY, yDot); + + // predict Nordsieck vector at step end + for (int j = 0; j < predictedScaled.length; ++j) { + predictedScaled[j] = hNew * yDot[j]; + } + predictedNordsieck = updateHighOrderDerivativesPhase1(nordsieck); + updateHighOrderDerivativesPhase2(scaled, predictedScaled, predictedNordsieck); + + // evaluate error + error = errorEstimation(y, predictedY, predictedScaled, predictedNordsieck); + + if (error >= 1.0) { + // reject the step and attempt to reduce error by stepsize control + final double factor = computeStepGrowShrinkFactor(error); + hNew = filterStep(hNew * factor, forward, false); + interpolator.rescale(hNew); + + } + } + + stepSize = hNew; + final double stepEnd = stepStart + stepSize; + interpolator.reinitialize(stepEnd, stepSize, predictedScaled, predictedNordsieck); + + // discrete events handling + interpolator.storeTime(stepEnd); + System.arraycopy(predictedY, 0, y, 0, y.length); + stepStart = acceptStep(interpolator, y, yDot, t); + scaled = predictedScaled; + nordsieck = predictedNordsieck; + interpolator.reinitialize(stepEnd, stepSize, scaled, nordsieck); + + if (!isLastStep) { + + // prepare next step + interpolator.storeTime(stepStart); + + if (resetOccurred) { + // some events handler has triggered changes that + // invalidate the derivatives, we need to restart from scratch + start(stepStart, y, t); + interpolator.reinitialize(stepStart, stepSize, scaled, nordsieck); + } + + // stepsize control for next step + final double factor = computeStepGrowShrinkFactor(error); + final double scaledH = stepSize * factor; + final double nextT = stepStart + scaledH; + final boolean nextIsLast = forward ? (nextT >= t) : (nextT <= t); + hNew = filterStep(scaledH, forward, nextIsLast); + + final double filteredNextT = stepStart + hNew; + final boolean filteredNextIsLast = forward ? (filteredNextT >= t) : (filteredNextT <= t); + if (filteredNextIsLast) { + hNew = t - stepStart; + } + + interpolator.rescale(hNew); + + } + + } while (!isLastStep); + + // dispatch results + equations.setTime(stepStart); + equations.setCompleteState(y); + + resetInternalState(); + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsFieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsFieldIntegrator.java new file mode 100644 index 0000000..fcd9397 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsFieldIntegrator.java @@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.Field; +import org.apache.commons.math3.RealFieldElement; +import org.apache.commons.math3.exception.DimensionMismatchException; +import org.apache.commons.math3.exception.MaxCountExceededException; +import org.apache.commons.math3.exception.NoBracketingException; +import org.apache.commons.math3.exception.NumberIsTooSmallException; +import org.apache.commons.math3.linear.Array2DRowFieldMatrix; +import org.apache.commons.math3.ode.FieldExpandableODE; +import org.apache.commons.math3.ode.FieldODEState; +import org.apache.commons.math3.ode.FieldODEStateAndDerivative; +import org.apache.commons.math3.ode.MultistepFieldIntegrator; + + +/** Base class for {@link AdamsBashforthFieldIntegrator Adams-Bashforth} and + * {@link AdamsMoultonFieldIntegrator Adams-Moulton} integrators. + * @param <T> the type of the field elements + * @since 3.6 + */ +public abstract class AdamsFieldIntegrator<T extends RealFieldElement<T>> extends MultistepFieldIntegrator<T> { + + /** Transformer. */ + private final AdamsNordsieckFieldTransformer<T> transformer; + + /** + * Build an Adams integrator with the given order and step control parameters. + * @param field field to which the time and state vector elements belong + * @param name name of the method + * @param nSteps number of steps of the method excluding the one being computed + * @param order order of the method + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + * @exception NumberIsTooSmallException if order is 1 or less + */ + public AdamsFieldIntegrator(final Field<T> field, final String name, + final int nSteps, final int order, + final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) + throws NumberIsTooSmallException { + super(field, name, nSteps, order, minStep, maxStep, + scalAbsoluteTolerance, scalRelativeTolerance); + transformer = AdamsNordsieckFieldTransformer.getInstance(field, nSteps); + } + + /** + * Build an Adams integrator with the given order and step control parameters. + * @param field field to which the time and state vector elements belong + * @param name name of the method + * @param nSteps number of steps of the method excluding the one being computed + * @param order order of the method + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + * @exception IllegalArgumentException if order is 1 or less + */ + public AdamsFieldIntegrator(final Field<T> field, final String name, + final int nSteps, final int order, + final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) + throws IllegalArgumentException { + super(field, name, nSteps, order, minStep, maxStep, + vecAbsoluteTolerance, vecRelativeTolerance); + transformer = AdamsNordsieckFieldTransformer.getInstance(field, nSteps); + } + + /** {@inheritDoc} */ + public abstract FieldODEStateAndDerivative<T> integrate(final FieldExpandableODE<T> equations, + final FieldODEState<T> initialState, + final T finalTime) + throws NumberIsTooSmallException, DimensionMismatchException, + MaxCountExceededException, NoBracketingException; + + /** {@inheritDoc} */ + @Override + protected Array2DRowFieldMatrix<T> initializeHighOrderDerivatives(final T h, final T[] t, + final T[][] y, + final T[][] yDot) { + return transformer.initializeHighOrderDerivatives(h, t, y, yDot); + } + + /** Update the high order scaled derivatives for Adams integrators (phase 1). + * <p>The complete update of high order derivatives has a form similar to: + * <pre> + * r<sub>n+1</sub> = (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub> + * </pre> + * this method computes the P<sup>-1</sup> A P r<sub>n</sub> part.</p> + * @param highOrder high order scaled derivatives + * (h<sup>2</sup>/2 y'', ... h<sup>k</sup>/k! y(k)) + * @return updated high order derivatives + * @see #updateHighOrderDerivativesPhase2(RealFieldElement[], RealFieldElement[], Array2DRowFieldMatrix) + */ + public Array2DRowFieldMatrix<T> updateHighOrderDerivativesPhase1(final Array2DRowFieldMatrix<T> highOrder) { + return transformer.updateHighOrderDerivativesPhase1(highOrder); + } + + /** Update the high order scaled derivatives Adams integrators (phase 2). + * <p>The complete update of high order derivatives has a form similar to: + * <pre> + * r<sub>n+1</sub> = (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub> + * </pre> + * this method computes the (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u part.</p> + * <p>Phase 1 of the update must already have been performed.</p> + * @param start first order scaled derivatives at step start + * @param end first order scaled derivatives at step end + * @param highOrder high order scaled derivatives, will be modified + * (h<sup>2</sup>/2 y'', ... h<sup>k</sup>/k! y(k)) + * @see #updateHighOrderDerivativesPhase1(Array2DRowFieldMatrix) + */ + public void updateHighOrderDerivativesPhase2(final T[] start, final T[] end, + final Array2DRowFieldMatrix<T> highOrder) { + transformer.updateHighOrderDerivativesPhase2(start, end, highOrder); + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsFieldStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsFieldStepInterpolator.java new file mode 100644 index 0000000..5de61cc --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsFieldStepInterpolator.java @@ -0,0 +1,189 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import java.util.Arrays; + +import org.apache.commons.math3.RealFieldElement; +import org.apache.commons.math3.linear.Array2DRowFieldMatrix; +import org.apache.commons.math3.ode.FieldEquationsMapper; +import org.apache.commons.math3.ode.FieldODEStateAndDerivative; +import org.apache.commons.math3.ode.sampling.AbstractFieldStepInterpolator; +import org.apache.commons.math3.util.MathArrays; + +/** + * This class implements an interpolator for Adams integrators using Nordsieck representation. + * + * <p>This interpolator computes dense output around the current point. + * The interpolation equation is based on Taylor series formulas. + * + * @see AdamsBashforthFieldIntegrator + * @see AdamsMoultonFieldIntegrator + * @param <T> the type of the field elements + * @since 3.6 + */ + +class AdamsFieldStepInterpolator<T extends RealFieldElement<T>> extends AbstractFieldStepInterpolator<T> { + + /** Step size used in the first scaled derivative and Nordsieck vector. */ + private T scalingH; + + /** Reference state. + * <p>Sometimes, the reference state is the same as globalPreviousState, + * sometimes it is the same as globalCurrentState, so we use a separate + * field to avoid any confusion. + * </p> + */ + private final FieldODEStateAndDerivative<T> reference; + + /** First scaled derivative. */ + private final T[] scaled; + + /** Nordsieck vector. */ + private final Array2DRowFieldMatrix<T> nordsieck; + + /** Simple constructor. + * @param stepSize step size used in the scaled and Nordsieck arrays + * @param reference reference state from which Taylor expansion are estimated + * @param scaled first scaled derivative + * @param nordsieck Nordsieck vector + * @param isForward integration direction indicator + * @param globalPreviousState start of the global step + * @param globalCurrentState end of the global step + * @param equationsMapper mapper for ODE equations primary and secondary components + */ + AdamsFieldStepInterpolator(final T stepSize, final FieldODEStateAndDerivative<T> reference, + final T[] scaled, final Array2DRowFieldMatrix<T> nordsieck, + final boolean isForward, + final FieldODEStateAndDerivative<T> globalPreviousState, + final FieldODEStateAndDerivative<T> globalCurrentState, + final FieldEquationsMapper<T> equationsMapper) { + this(stepSize, reference, scaled, nordsieck, + isForward, globalPreviousState, globalCurrentState, + globalPreviousState, globalCurrentState, equationsMapper); + } + + /** Simple constructor. + * @param stepSize step size used in the scaled and Nordsieck arrays + * @param reference reference state from which Taylor expansion are estimated + * @param scaled first scaled derivative + * @param nordsieck Nordsieck vector + * @param isForward integration direction indicator + * @param globalPreviousState start of the global step + * @param globalCurrentState end of the global step + * @param softPreviousState start of the restricted step + * @param softCurrentState end of the restricted step + * @param equationsMapper mapper for ODE equations primary and secondary components + */ + private AdamsFieldStepInterpolator(final T stepSize, final FieldODEStateAndDerivative<T> reference, + final T[] scaled, final Array2DRowFieldMatrix<T> nordsieck, + final boolean isForward, + final FieldODEStateAndDerivative<T> globalPreviousState, + final FieldODEStateAndDerivative<T> globalCurrentState, + final FieldODEStateAndDerivative<T> softPreviousState, + final FieldODEStateAndDerivative<T> softCurrentState, + final FieldEquationsMapper<T> equationsMapper) { + super(isForward, globalPreviousState, globalCurrentState, + softPreviousState, softCurrentState, equationsMapper); + this.scalingH = stepSize; + this.reference = reference; + this.scaled = scaled.clone(); + this.nordsieck = new Array2DRowFieldMatrix<T>(nordsieck.getData(), false); + } + + /** Create a new instance. + * @param newForward integration direction indicator + * @param newGlobalPreviousState start of the global step + * @param newGlobalCurrentState end of the global step + * @param newSoftPreviousState start of the restricted step + * @param newSoftCurrentState end of the restricted step + * @param newMapper equations mapper for the all equations + * @return a new instance + */ + @Override + protected AdamsFieldStepInterpolator<T> create(boolean newForward, + FieldODEStateAndDerivative<T> newGlobalPreviousState, + FieldODEStateAndDerivative<T> newGlobalCurrentState, + FieldODEStateAndDerivative<T> newSoftPreviousState, + FieldODEStateAndDerivative<T> newSoftCurrentState, + FieldEquationsMapper<T> newMapper) { + return new AdamsFieldStepInterpolator<T>(scalingH, reference, scaled, nordsieck, + newForward, + newGlobalPreviousState, newGlobalCurrentState, + newSoftPreviousState, newSoftCurrentState, + newMapper); + + } + + /** {@inheritDoc} */ + @Override + protected FieldODEStateAndDerivative<T> computeInterpolatedStateAndDerivatives(final FieldEquationsMapper<T> equationsMapper, + final T time, final T theta, + final T thetaH, final T oneMinusThetaH) { + return taylor(reference, time, scalingH, scaled, nordsieck); + } + + /** Estimate state by applying Taylor formula. + * @param reference reference state + * @param time time at which state must be estimated + * @param stepSize step size used in the scaled and Nordsieck arrays + * @param scaled first scaled derivative + * @param nordsieck Nordsieck vector + * @return estimated state + * @param <S> the type of the field elements + */ + public static <S extends RealFieldElement<S>> FieldODEStateAndDerivative<S> taylor(final FieldODEStateAndDerivative<S> reference, + final S time, final S stepSize, + final S[] scaled, + final Array2DRowFieldMatrix<S> nordsieck) { + + final S x = time.subtract(reference.getTime()); + final S normalizedAbscissa = x.divide(stepSize); + + S[] stateVariation = MathArrays.buildArray(time.getField(), scaled.length); + Arrays.fill(stateVariation, time.getField().getZero()); + S[] estimatedDerivatives = MathArrays.buildArray(time.getField(), scaled.length); + Arrays.fill(estimatedDerivatives, time.getField().getZero()); + + // apply Taylor formula from high order to low order, + // for the sake of numerical accuracy + final S[][] nData = nordsieck.getDataRef(); + for (int i = nData.length - 1; i >= 0; --i) { + final int order = i + 2; + final S[] nDataI = nData[i]; + final S power = normalizedAbscissa.pow(order); + for (int j = 0; j < nDataI.length; ++j) { + final S d = nDataI[j].multiply(power); + stateVariation[j] = stateVariation[j].add(d); + estimatedDerivatives[j] = estimatedDerivatives[j].add(d.multiply(order)); + } + } + + S[] estimatedState = reference.getState(); + for (int j = 0; j < stateVariation.length; ++j) { + stateVariation[j] = stateVariation[j].add(scaled[j].multiply(normalizedAbscissa)); + estimatedState[j] = estimatedState[j].add(stateVariation[j]); + estimatedDerivatives[j] = + estimatedDerivatives[j].add(scaled[j].multiply(normalizedAbscissa)).divide(x); + } + + return new FieldODEStateAndDerivative<S>(time, estimatedState, estimatedDerivatives); + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsIntegrator.java new file mode 100644 index 0000000..dd6f2a1 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsIntegrator.java @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.exception.DimensionMismatchException; +import org.apache.commons.math3.exception.MaxCountExceededException; +import org.apache.commons.math3.exception.NoBracketingException; +import org.apache.commons.math3.exception.NumberIsTooSmallException; +import org.apache.commons.math3.linear.Array2DRowRealMatrix; +import org.apache.commons.math3.ode.ExpandableStatefulODE; +import org.apache.commons.math3.ode.MultistepIntegrator; + + +/** Base class for {@link AdamsBashforthIntegrator Adams-Bashforth} and + * {@link AdamsMoultonIntegrator Adams-Moulton} integrators. + * @since 2.0 + */ +public abstract class AdamsIntegrator extends MultistepIntegrator { + + /** Transformer. */ + private final AdamsNordsieckTransformer transformer; + + /** + * Build an Adams integrator with the given order and step control parameters. + * @param name name of the method + * @param nSteps number of steps of the method excluding the one being computed + * @param order order of the method + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + * @exception NumberIsTooSmallException if order is 1 or less + */ + public AdamsIntegrator(final String name, final int nSteps, final int order, + final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) + throws NumberIsTooSmallException { + super(name, nSteps, order, minStep, maxStep, + scalAbsoluteTolerance, scalRelativeTolerance); + transformer = AdamsNordsieckTransformer.getInstance(nSteps); + } + + /** + * Build an Adams integrator with the given order and step control parameters. + * @param name name of the method + * @param nSteps number of steps of the method excluding the one being computed + * @param order order of the method + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + * @exception IllegalArgumentException if order is 1 or less + */ + public AdamsIntegrator(final String name, final int nSteps, final int order, + final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) + throws IllegalArgumentException { + super(name, nSteps, order, minStep, maxStep, + vecAbsoluteTolerance, vecRelativeTolerance); + transformer = AdamsNordsieckTransformer.getInstance(nSteps); + } + + /** {@inheritDoc} */ + @Override + public abstract void integrate(final ExpandableStatefulODE equations, final double t) + throws NumberIsTooSmallException, DimensionMismatchException, + MaxCountExceededException, NoBracketingException; + + /** {@inheritDoc} */ + @Override + protected Array2DRowRealMatrix initializeHighOrderDerivatives(final double h, final double[] t, + final double[][] y, + final double[][] yDot) { + return transformer.initializeHighOrderDerivatives(h, t, y, yDot); + } + + /** Update the high order scaled derivatives for Adams integrators (phase 1). + * <p>The complete update of high order derivatives has a form similar to: + * <pre> + * r<sub>n+1</sub> = (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub> + * </pre> + * this method computes the P<sup>-1</sup> A P r<sub>n</sub> part.</p> + * @param highOrder high order scaled derivatives + * (h<sup>2</sup>/2 y'', ... h<sup>k</sup>/k! y(k)) + * @return updated high order derivatives + * @see #updateHighOrderDerivativesPhase2(double[], double[], Array2DRowRealMatrix) + */ + public Array2DRowRealMatrix updateHighOrderDerivativesPhase1(final Array2DRowRealMatrix highOrder) { + return transformer.updateHighOrderDerivativesPhase1(highOrder); + } + + /** Update the high order scaled derivatives Adams integrators (phase 2). + * <p>The complete update of high order derivatives has a form similar to: + * <pre> + * r<sub>n+1</sub> = (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub> + * </pre> + * this method computes the (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u part.</p> + * <p>Phase 1 of the update must already have been performed.</p> + * @param start first order scaled derivatives at step start + * @param end first order scaled derivatives at step end + * @param highOrder high order scaled derivatives, will be modified + * (h<sup>2</sup>/2 y'', ... h<sup>k</sup>/k! y(k)) + * @see #updateHighOrderDerivativesPhase1(Array2DRowRealMatrix) + */ + public void updateHighOrderDerivativesPhase2(final double[] start, + final double[] end, + final Array2DRowRealMatrix highOrder) { + transformer.updateHighOrderDerivativesPhase2(start, end, highOrder); + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsMoultonFieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsMoultonFieldIntegrator.java new file mode 100644 index 0000000..2594321 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsMoultonFieldIntegrator.java @@ -0,0 +1,416 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import java.util.Arrays; + +import org.apache.commons.math3.Field; +import org.apache.commons.math3.RealFieldElement; +import org.apache.commons.math3.exception.DimensionMismatchException; +import org.apache.commons.math3.exception.MaxCountExceededException; +import org.apache.commons.math3.exception.NoBracketingException; +import org.apache.commons.math3.exception.NumberIsTooSmallException; +import org.apache.commons.math3.linear.Array2DRowFieldMatrix; +import org.apache.commons.math3.linear.FieldMatrixPreservingVisitor; +import org.apache.commons.math3.ode.FieldExpandableODE; +import org.apache.commons.math3.ode.FieldODEState; +import org.apache.commons.math3.ode.FieldODEStateAndDerivative; +import org.apache.commons.math3.util.MathArrays; +import org.apache.commons.math3.util.MathUtils; + + +/** + * This class implements implicit Adams-Moulton integrators for Ordinary + * Differential Equations. + * + * <p>Adams-Moulton methods (in fact due to Adams alone) are implicit + * multistep ODE solvers. This implementation is a variation of the classical + * one: it uses adaptive stepsize to implement error control, whereas + * classical implementations are fixed step size. The value of state vector + * at step n+1 is a simple combination of the value at step n and of the + * derivatives at steps n+1, n, n-1 ... Since y'<sub>n+1</sub> is needed to + * compute y<sub>n+1</sub>, another method must be used to compute a first + * estimate of y<sub>n+1</sub>, then compute y'<sub>n+1</sub>, then compute + * a final estimate of y<sub>n+1</sub> using the following formulas. Depending + * on the number k of previous steps one wants to use for computing the next + * value, different formulas are available for the final estimate:</p> + * <ul> + * <li>k = 1: y<sub>n+1</sub> = y<sub>n</sub> + h y'<sub>n+1</sub></li> + * <li>k = 2: y<sub>n+1</sub> = y<sub>n</sub> + h (y'<sub>n+1</sub>+y'<sub>n</sub>)/2</li> + * <li>k = 3: y<sub>n+1</sub> = y<sub>n</sub> + h (5y'<sub>n+1</sub>+8y'<sub>n</sub>-y'<sub>n-1</sub>)/12</li> + * <li>k = 4: y<sub>n+1</sub> = y<sub>n</sub> + h (9y'<sub>n+1</sub>+19y'<sub>n</sub>-5y'<sub>n-1</sub>+y'<sub>n-2</sub>)/24</li> + * <li>...</li> + * </ul> + * + * <p>A k-steps Adams-Moulton method is of order k+1.</p> + * + * <h3>Implementation details</h3> + * + * <p>We define scaled derivatives s<sub>i</sub>(n) at step n as: + * <pre> + * s<sub>1</sub>(n) = h y'<sub>n</sub> for first derivative + * s<sub>2</sub>(n) = h<sup>2</sup>/2 y''<sub>n</sub> for second derivative + * s<sub>3</sub>(n) = h<sup>3</sup>/6 y'''<sub>n</sub> for third derivative + * ... + * s<sub>k</sub>(n) = h<sup>k</sup>/k! y<sup>(k)</sup><sub>n</sub> for k<sup>th</sup> derivative + * </pre></p> + * + * <p>The definitions above use the classical representation with several previous first + * derivatives. Lets define + * <pre> + * q<sub>n</sub> = [ s<sub>1</sub>(n-1) s<sub>1</sub>(n-2) ... s<sub>1</sub>(n-(k-1)) ]<sup>T</sup> + * </pre> + * (we omit the k index in the notation for clarity). With these definitions, + * Adams-Moulton methods can be written: + * <ul> + * <li>k = 1: y<sub>n+1</sub> = y<sub>n</sub> + s<sub>1</sub>(n+1)</li> + * <li>k = 2: y<sub>n+1</sub> = y<sub>n</sub> + 1/2 s<sub>1</sub>(n+1) + [ 1/2 ] q<sub>n+1</sub></li> + * <li>k = 3: y<sub>n+1</sub> = y<sub>n</sub> + 5/12 s<sub>1</sub>(n+1) + [ 8/12 -1/12 ] q<sub>n+1</sub></li> + * <li>k = 4: y<sub>n+1</sub> = y<sub>n</sub> + 9/24 s<sub>1</sub>(n+1) + [ 19/24 -5/24 1/24 ] q<sub>n+1</sub></li> + * <li>...</li> + * </ul></p> + * + * <p>Instead of using the classical representation with first derivatives only (y<sub>n</sub>, + * s<sub>1</sub>(n+1) and q<sub>n+1</sub>), our implementation uses the Nordsieck vector with + * higher degrees scaled derivatives all taken at the same step (y<sub>n</sub>, s<sub>1</sub>(n) + * and r<sub>n</sub>) where r<sub>n</sub> is defined as: + * <pre> + * r<sub>n</sub> = [ s<sub>2</sub>(n), s<sub>3</sub>(n) ... s<sub>k</sub>(n) ]<sup>T</sup> + * </pre> + * (here again we omit the k index in the notation for clarity) + * </p> + * + * <p>Taylor series formulas show that for any index offset i, s<sub>1</sub>(n-i) can be + * computed from s<sub>1</sub>(n), s<sub>2</sub>(n) ... s<sub>k</sub>(n), the formula being exact + * for degree k polynomials. + * <pre> + * s<sub>1</sub>(n-i) = s<sub>1</sub>(n) + ∑<sub>j>0</sub> (j+1) (-i)<sup>j</sup> s<sub>j+1</sub>(n) + * </pre> + * The previous formula can be used with several values for i to compute the transform between + * classical representation and Nordsieck vector. The transform between r<sub>n</sub> + * and q<sub>n</sub> resulting from the Taylor series formulas above is: + * <pre> + * q<sub>n</sub> = s<sub>1</sub>(n) u + P r<sub>n</sub> + * </pre> + * where u is the [ 1 1 ... 1 ]<sup>T</sup> vector and P is the (k-1)×(k-1) matrix built + * with the (j+1) (-i)<sup>j</sup> terms with i being the row number starting from 1 and j being + * the column number starting from 1: + * <pre> + * [ -2 3 -4 5 ... ] + * [ -4 12 -32 80 ... ] + * P = [ -6 27 -108 405 ... ] + * [ -8 48 -256 1280 ... ] + * [ ... ] + * </pre></p> + * + * <p>Using the Nordsieck vector has several advantages: + * <ul> + * <li>it greatly simplifies step interpolation as the interpolator mainly applies + * Taylor series formulas,</li> + * <li>it simplifies step changes that occur when discrete events that truncate + * the step are triggered,</li> + * <li>it allows to extend the methods in order to support adaptive stepsize.</li> + * </ul></p> + * + * <p>The predicted Nordsieck vector at step n+1 is computed from the Nordsieck vector at step + * n as follows: + * <ul> + * <li>Y<sub>n+1</sub> = y<sub>n</sub> + s<sub>1</sub>(n) + u<sup>T</sup> r<sub>n</sub></li> + * <li>S<sub>1</sub>(n+1) = h f(t<sub>n+1</sub>, Y<sub>n+1</sub>)</li> + * <li>R<sub>n+1</sub> = (s<sub>1</sub>(n) - S<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub></li> + * </ul> + * where A is a rows shifting matrix (the lower left part is an identity matrix): + * <pre> + * [ 0 0 ... 0 0 | 0 ] + * [ ---------------+---] + * [ 1 0 ... 0 0 | 0 ] + * A = [ 0 1 ... 0 0 | 0 ] + * [ ... | 0 ] + * [ 0 0 ... 1 0 | 0 ] + * [ 0 0 ... 0 1 | 0 ] + * </pre> + * From this predicted vector, the corrected vector is computed as follows: + * <ul> + * <li>y<sub>n+1</sub> = y<sub>n</sub> + S<sub>1</sub>(n+1) + [ -1 +1 -1 +1 ... ±1 ] r<sub>n+1</sub></li> + * <li>s<sub>1</sub>(n+1) = h f(t<sub>n+1</sub>, y<sub>n+1</sub>)</li> + * <li>r<sub>n+1</sub> = R<sub>n+1</sub> + (s<sub>1</sub>(n+1) - S<sub>1</sub>(n+1)) P<sup>-1</sup> u</li> + * </ul> + * where the upper case Y<sub>n+1</sub>, S<sub>1</sub>(n+1) and R<sub>n+1</sub> represent the + * predicted states whereas the lower case y<sub>n+1</sub>, s<sub>n+1</sub> and r<sub>n+1</sub> + * represent the corrected states.</p> + * + * <p>The P<sup>-1</sup>u vector and the P<sup>-1</sup> A P matrix do not depend on the state, + * they only depend on k and therefore are precomputed once for all.</p> + * + * @param <T> the type of the field elements + * @since 3.6 + */ +public class AdamsMoultonFieldIntegrator<T extends RealFieldElement<T>> extends AdamsFieldIntegrator<T> { + + /** Integrator method name. */ + private static final String METHOD_NAME = "Adams-Moulton"; + + /** + * Build an Adams-Moulton integrator with the given order and error control parameters. + * @param field field to which the time and state vector elements belong + * @param nSteps number of steps of the method excluding the one being computed + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + * @exception NumberIsTooSmallException if order is 1 or less + */ + public AdamsMoultonFieldIntegrator(final Field<T> field, final int nSteps, + final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) + throws NumberIsTooSmallException { + super(field, METHOD_NAME, nSteps, nSteps + 1, minStep, maxStep, + scalAbsoluteTolerance, scalRelativeTolerance); + } + + /** + * Build an Adams-Moulton integrator with the given order and error control parameters. + * @param field field to which the time and state vector elements belong + * @param nSteps number of steps of the method excluding the one being computed + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + * @exception IllegalArgumentException if order is 1 or less + */ + public AdamsMoultonFieldIntegrator(final Field<T> field, final int nSteps, + final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) + throws IllegalArgumentException { + super(field, METHOD_NAME, nSteps, nSteps + 1, minStep, maxStep, + vecAbsoluteTolerance, vecRelativeTolerance); + } + + /** {@inheritDoc} */ + @Override + public FieldODEStateAndDerivative<T> integrate(final FieldExpandableODE<T> equations, + final FieldODEState<T> initialState, + final T finalTime) + throws NumberIsTooSmallException, DimensionMismatchException, + MaxCountExceededException, NoBracketingException { + + sanityChecks(initialState, finalTime); + final T t0 = initialState.getTime(); + final T[] y = equations.getMapper().mapState(initialState); + setStepStart(initIntegration(equations, t0, y, finalTime)); + final boolean forward = finalTime.subtract(initialState.getTime()).getReal() > 0; + + // compute the initial Nordsieck vector using the configured starter integrator + start(equations, getStepStart(), finalTime); + + // reuse the step that was chosen by the starter integrator + FieldODEStateAndDerivative<T> stepStart = getStepStart(); + FieldODEStateAndDerivative<T> stepEnd = + AdamsFieldStepInterpolator.taylor(stepStart, + stepStart.getTime().add(getStepSize()), + getStepSize(), scaled, nordsieck); + + // main integration loop + setIsLastStep(false); + do { + + T[] predictedY = null; + final T[] predictedScaled = MathArrays.buildArray(getField(), y.length); + Array2DRowFieldMatrix<T> predictedNordsieck = null; + T error = getField().getZero().add(10); + while (error.subtract(1.0).getReal() >= 0.0) { + + // predict a first estimate of the state at step end (P in the PECE sequence) + predictedY = stepEnd.getState(); + + // evaluate a first estimate of the derivative (first E in the PECE sequence) + final T[] yDot = computeDerivatives(stepEnd.getTime(), predictedY); + + // update Nordsieck vector + for (int j = 0; j < predictedScaled.length; ++j) { + predictedScaled[j] = getStepSize().multiply(yDot[j]); + } + predictedNordsieck = updateHighOrderDerivativesPhase1(nordsieck); + updateHighOrderDerivativesPhase2(scaled, predictedScaled, predictedNordsieck); + + // apply correction (C in the PECE sequence) + error = predictedNordsieck.walkInOptimizedOrder(new Corrector(y, predictedScaled, predictedY)); + + if (error.subtract(1.0).getReal() >= 0.0) { + // reject the step and attempt to reduce error by stepsize control + final T factor = computeStepGrowShrinkFactor(error); + rescale(filterStep(getStepSize().multiply(factor), forward, false)); + stepEnd = AdamsFieldStepInterpolator.taylor(getStepStart(), + getStepStart().getTime().add(getStepSize()), + getStepSize(), + scaled, + nordsieck); + } + } + + // evaluate a final estimate of the derivative (second E in the PECE sequence) + final T[] correctedYDot = computeDerivatives(stepEnd.getTime(), predictedY); + + // update Nordsieck vector + final T[] correctedScaled = MathArrays.buildArray(getField(), y.length); + for (int j = 0; j < correctedScaled.length; ++j) { + correctedScaled[j] = getStepSize().multiply(correctedYDot[j]); + } + updateHighOrderDerivativesPhase2(predictedScaled, correctedScaled, predictedNordsieck); + + // discrete events handling + stepEnd = new FieldODEStateAndDerivative<T>(stepEnd.getTime(), predictedY, correctedYDot); + setStepStart(acceptStep(new AdamsFieldStepInterpolator<T>(getStepSize(), stepEnd, + correctedScaled, predictedNordsieck, forward, + getStepStart(), stepEnd, + equations.getMapper()), + finalTime)); + scaled = correctedScaled; + nordsieck = predictedNordsieck; + + if (!isLastStep()) { + + System.arraycopy(predictedY, 0, y, 0, y.length); + + if (resetOccurred()) { + // some events handler has triggered changes that + // invalidate the derivatives, we need to restart from scratch + start(equations, getStepStart(), finalTime); + } + + // stepsize control for next step + final T factor = computeStepGrowShrinkFactor(error); + final T scaledH = getStepSize().multiply(factor); + final T nextT = getStepStart().getTime().add(scaledH); + final boolean nextIsLast = forward ? + nextT.subtract(finalTime).getReal() >= 0 : + nextT.subtract(finalTime).getReal() <= 0; + T hNew = filterStep(scaledH, forward, nextIsLast); + + final T filteredNextT = getStepStart().getTime().add(hNew); + final boolean filteredNextIsLast = forward ? + filteredNextT.subtract(finalTime).getReal() >= 0 : + filteredNextT.subtract(finalTime).getReal() <= 0; + if (filteredNextIsLast) { + hNew = finalTime.subtract(getStepStart().getTime()); + } + + rescale(hNew); + stepEnd = AdamsFieldStepInterpolator.taylor(getStepStart(), getStepStart().getTime().add(getStepSize()), + getStepSize(), scaled, nordsieck); + + } + + } while (!isLastStep()); + + final FieldODEStateAndDerivative<T> finalState = getStepStart(); + setStepStart(null); + setStepSize(null); + return finalState; + + } + + /** Corrector for current state in Adams-Moulton method. + * <p> + * This visitor implements the Taylor series formula: + * <pre> + * Y<sub>n+1</sub> = y<sub>n</sub> + s<sub>1</sub>(n+1) + [ -1 +1 -1 +1 ... ±1 ] r<sub>n+1</sub> + * </pre> + * </p> + */ + private class Corrector implements FieldMatrixPreservingVisitor<T> { + + /** Previous state. */ + private final T[] previous; + + /** Current scaled first derivative. */ + private final T[] scaled; + + /** Current state before correction. */ + private final T[] before; + + /** Current state after correction. */ + private final T[] after; + + /** Simple constructor. + * @param previous previous state + * @param scaled current scaled first derivative + * @param state state to correct (will be overwritten after visit) + */ + Corrector(final T[] previous, final T[] scaled, final T[] state) { + this.previous = previous; + this.scaled = scaled; + this.after = state; + this.before = state.clone(); + } + + /** {@inheritDoc} */ + public void start(int rows, int columns, + int startRow, int endRow, int startColumn, int endColumn) { + Arrays.fill(after, getField().getZero()); + } + + /** {@inheritDoc} */ + public void visit(int row, int column, T value) { + if ((row & 0x1) == 0) { + after[column] = after[column].subtract(value); + } else { + after[column] = after[column].add(value); + } + } + + /** + * End visiting the Nordsieck vector. + * <p>The correction is used to control stepsize. So its amplitude is + * considered to be an error, which must be normalized according to + * error control settings. If the normalized value is greater than 1, + * the correction was too large and the step must be rejected.</p> + * @return the normalized correction, if greater than 1, the step + * must be rejected + */ + public T end() { + + T error = getField().getZero(); + for (int i = 0; i < after.length; ++i) { + after[i] = after[i].add(previous[i].add(scaled[i])); + if (i < mainSetDimension) { + final T yScale = MathUtils.max(previous[i].abs(), after[i].abs()); + final T tol = (vecAbsoluteTolerance == null) ? + yScale.multiply(scalRelativeTolerance).add(scalAbsoluteTolerance) : + yScale.multiply(vecRelativeTolerance[i]).add(vecAbsoluteTolerance[i]); + final T ratio = after[i].subtract(before[i]).divide(tol); // (corrected-predicted)/tol + error = error.add(ratio.multiply(ratio)); + } + } + + return error.divide(mainSetDimension).sqrt(); + + } + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsMoultonIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsMoultonIntegrator.java new file mode 100644 index 0000000..69d2469 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsMoultonIntegrator.java @@ -0,0 +1,421 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import java.util.Arrays; + +import org.apache.commons.math3.exception.DimensionMismatchException; +import org.apache.commons.math3.exception.MaxCountExceededException; +import org.apache.commons.math3.exception.NoBracketingException; +import org.apache.commons.math3.exception.NumberIsTooSmallException; +import org.apache.commons.math3.linear.Array2DRowRealMatrix; +import org.apache.commons.math3.linear.RealMatrixPreservingVisitor; +import org.apache.commons.math3.ode.EquationsMapper; +import org.apache.commons.math3.ode.ExpandableStatefulODE; +import org.apache.commons.math3.ode.sampling.NordsieckStepInterpolator; +import org.apache.commons.math3.util.FastMath; + + +/** + * This class implements implicit Adams-Moulton integrators for Ordinary + * Differential Equations. + * + * <p>Adams-Moulton methods (in fact due to Adams alone) are implicit + * multistep ODE solvers. This implementation is a variation of the classical + * one: it uses adaptive stepsize to implement error control, whereas + * classical implementations are fixed step size. The value of state vector + * at step n+1 is a simple combination of the value at step n and of the + * derivatives at steps n+1, n, n-1 ... Since y'<sub>n+1</sub> is needed to + * compute y<sub>n+1</sub>, another method must be used to compute a first + * estimate of y<sub>n+1</sub>, then compute y'<sub>n+1</sub>, then compute + * a final estimate of y<sub>n+1</sub> using the following formulas. Depending + * on the number k of previous steps one wants to use for computing the next + * value, different formulas are available for the final estimate:</p> + * <ul> + * <li>k = 1: y<sub>n+1</sub> = y<sub>n</sub> + h y'<sub>n+1</sub></li> + * <li>k = 2: y<sub>n+1</sub> = y<sub>n</sub> + h (y'<sub>n+1</sub>+y'<sub>n</sub>)/2</li> + * <li>k = 3: y<sub>n+1</sub> = y<sub>n</sub> + h (5y'<sub>n+1</sub>+8y'<sub>n</sub>-y'<sub>n-1</sub>)/12</li> + * <li>k = 4: y<sub>n+1</sub> = y<sub>n</sub> + h (9y'<sub>n+1</sub>+19y'<sub>n</sub>-5y'<sub>n-1</sub>+y'<sub>n-2</sub>)/24</li> + * <li>...</li> + * </ul> + * + * <p>A k-steps Adams-Moulton method is of order k+1.</p> + * + * <h3>Implementation details</h3> + * + * <p>We define scaled derivatives s<sub>i</sub>(n) at step n as: + * <pre> + * s<sub>1</sub>(n) = h y'<sub>n</sub> for first derivative + * s<sub>2</sub>(n) = h<sup>2</sup>/2 y''<sub>n</sub> for second derivative + * s<sub>3</sub>(n) = h<sup>3</sup>/6 y'''<sub>n</sub> for third derivative + * ... + * s<sub>k</sub>(n) = h<sup>k</sup>/k! y<sup>(k)</sup><sub>n</sub> for k<sup>th</sup> derivative + * </pre></p> + * + * <p>The definitions above use the classical representation with several previous first + * derivatives. Lets define + * <pre> + * q<sub>n</sub> = [ s<sub>1</sub>(n-1) s<sub>1</sub>(n-2) ... s<sub>1</sub>(n-(k-1)) ]<sup>T</sup> + * </pre> + * (we omit the k index in the notation for clarity). With these definitions, + * Adams-Moulton methods can be written: + * <ul> + * <li>k = 1: y<sub>n+1</sub> = y<sub>n</sub> + s<sub>1</sub>(n+1)</li> + * <li>k = 2: y<sub>n+1</sub> = y<sub>n</sub> + 1/2 s<sub>1</sub>(n+1) + [ 1/2 ] q<sub>n+1</sub></li> + * <li>k = 3: y<sub>n+1</sub> = y<sub>n</sub> + 5/12 s<sub>1</sub>(n+1) + [ 8/12 -1/12 ] q<sub>n+1</sub></li> + * <li>k = 4: y<sub>n+1</sub> = y<sub>n</sub> + 9/24 s<sub>1</sub>(n+1) + [ 19/24 -5/24 1/24 ] q<sub>n+1</sub></li> + * <li>...</li> + * </ul></p> + * + * <p>Instead of using the classical representation with first derivatives only (y<sub>n</sub>, + * s<sub>1</sub>(n+1) and q<sub>n+1</sub>), our implementation uses the Nordsieck vector with + * higher degrees scaled derivatives all taken at the same step (y<sub>n</sub>, s<sub>1</sub>(n) + * and r<sub>n</sub>) where r<sub>n</sub> is defined as: + * <pre> + * r<sub>n</sub> = [ s<sub>2</sub>(n), s<sub>3</sub>(n) ... s<sub>k</sub>(n) ]<sup>T</sup> + * </pre> + * (here again we omit the k index in the notation for clarity) + * </p> + * + * <p>Taylor series formulas show that for any index offset i, s<sub>1</sub>(n-i) can be + * computed from s<sub>1</sub>(n), s<sub>2</sub>(n) ... s<sub>k</sub>(n), the formula being exact + * for degree k polynomials. + * <pre> + * s<sub>1</sub>(n-i) = s<sub>1</sub>(n) + ∑<sub>j>0</sub> (j+1) (-i)<sup>j</sup> s<sub>j+1</sub>(n) + * </pre> + * The previous formula can be used with several values for i to compute the transform between + * classical representation and Nordsieck vector. The transform between r<sub>n</sub> + * and q<sub>n</sub> resulting from the Taylor series formulas above is: + * <pre> + * q<sub>n</sub> = s<sub>1</sub>(n) u + P r<sub>n</sub> + * </pre> + * where u is the [ 1 1 ... 1 ]<sup>T</sup> vector and P is the (k-1)×(k-1) matrix built + * with the (j+1) (-i)<sup>j</sup> terms with i being the row number starting from 1 and j being + * the column number starting from 1: + * <pre> + * [ -2 3 -4 5 ... ] + * [ -4 12 -32 80 ... ] + * P = [ -6 27 -108 405 ... ] + * [ -8 48 -256 1280 ... ] + * [ ... ] + * </pre></p> + * + * <p>Using the Nordsieck vector has several advantages: + * <ul> + * <li>it greatly simplifies step interpolation as the interpolator mainly applies + * Taylor series formulas,</li> + * <li>it simplifies step changes that occur when discrete events that truncate + * the step are triggered,</li> + * <li>it allows to extend the methods in order to support adaptive stepsize.</li> + * </ul></p> + * + * <p>The predicted Nordsieck vector at step n+1 is computed from the Nordsieck vector at step + * n as follows: + * <ul> + * <li>Y<sub>n+1</sub> = y<sub>n</sub> + s<sub>1</sub>(n) + u<sup>T</sup> r<sub>n</sub></li> + * <li>S<sub>1</sub>(n+1) = h f(t<sub>n+1</sub>, Y<sub>n+1</sub>)</li> + * <li>R<sub>n+1</sub> = (s<sub>1</sub>(n) - S<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub></li> + * </ul> + * where A is a rows shifting matrix (the lower left part is an identity matrix): + * <pre> + * [ 0 0 ... 0 0 | 0 ] + * [ ---------------+---] + * [ 1 0 ... 0 0 | 0 ] + * A = [ 0 1 ... 0 0 | 0 ] + * [ ... | 0 ] + * [ 0 0 ... 1 0 | 0 ] + * [ 0 0 ... 0 1 | 0 ] + * </pre> + * From this predicted vector, the corrected vector is computed as follows: + * <ul> + * <li>y<sub>n+1</sub> = y<sub>n</sub> + S<sub>1</sub>(n+1) + [ -1 +1 -1 +1 ... ±1 ] r<sub>n+1</sub></li> + * <li>s<sub>1</sub>(n+1) = h f(t<sub>n+1</sub>, y<sub>n+1</sub>)</li> + * <li>r<sub>n+1</sub> = R<sub>n+1</sub> + (s<sub>1</sub>(n+1) - S<sub>1</sub>(n+1)) P<sup>-1</sup> u</li> + * </ul> + * where the upper case Y<sub>n+1</sub>, S<sub>1</sub>(n+1) and R<sub>n+1</sub> represent the + * predicted states whereas the lower case y<sub>n+1</sub>, s<sub>n+1</sub> and r<sub>n+1</sub> + * represent the corrected states.</p> + * + * <p>The P<sup>-1</sup>u vector and the P<sup>-1</sup> A P matrix do not depend on the state, + * they only depend on k and therefore are precomputed once for all.</p> + * + * @since 2.0 + */ +public class AdamsMoultonIntegrator extends AdamsIntegrator { + + /** Integrator method name. */ + private static final String METHOD_NAME = "Adams-Moulton"; + + /** + * Build an Adams-Moulton integrator with the given order and error control parameters. + * @param nSteps number of steps of the method excluding the one being computed + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + * @exception NumberIsTooSmallException if order is 1 or less + */ + public AdamsMoultonIntegrator(final int nSteps, + final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) + throws NumberIsTooSmallException { + super(METHOD_NAME, nSteps, nSteps + 1, minStep, maxStep, + scalAbsoluteTolerance, scalRelativeTolerance); + } + + /** + * Build an Adams-Moulton integrator with the given order and error control parameters. + * @param nSteps number of steps of the method excluding the one being computed + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + * @exception IllegalArgumentException if order is 1 or less + */ + public AdamsMoultonIntegrator(final int nSteps, + final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) + throws IllegalArgumentException { + super(METHOD_NAME, nSteps, nSteps + 1, minStep, maxStep, + vecAbsoluteTolerance, vecRelativeTolerance); + } + + /** {@inheritDoc} */ + @Override + public void integrate(final ExpandableStatefulODE equations,final double t) + throws NumberIsTooSmallException, DimensionMismatchException, + MaxCountExceededException, NoBracketingException { + + sanityChecks(equations, t); + setEquations(equations); + final boolean forward = t > equations.getTime(); + + // initialize working arrays + final double[] y0 = equations.getCompleteState(); + final double[] y = y0.clone(); + final double[] yDot = new double[y.length]; + final double[] yTmp = new double[y.length]; + final double[] predictedScaled = new double[y.length]; + Array2DRowRealMatrix nordsieckTmp = null; + + // set up two interpolators sharing the integrator arrays + final NordsieckStepInterpolator interpolator = new NordsieckStepInterpolator(); + interpolator.reinitialize(y, forward, + equations.getPrimaryMapper(), equations.getSecondaryMappers()); + + // set up integration control objects + initIntegration(equations.getTime(), y0, t); + + // compute the initial Nordsieck vector using the configured starter integrator + start(equations.getTime(), y, t); + interpolator.reinitialize(stepStart, stepSize, scaled, nordsieck); + interpolator.storeTime(stepStart); + + double hNew = stepSize; + interpolator.rescale(hNew); + + isLastStep = false; + do { + + double error = 10; + while (error >= 1.0) { + + stepSize = hNew; + + // predict a first estimate of the state at step end (P in the PECE sequence) + final double stepEnd = stepStart + stepSize; + interpolator.setInterpolatedTime(stepEnd); + final ExpandableStatefulODE expandable = getExpandable(); + final EquationsMapper primary = expandable.getPrimaryMapper(); + primary.insertEquationData(interpolator.getInterpolatedState(), yTmp); + int index = 0; + for (final EquationsMapper secondary : expandable.getSecondaryMappers()) { + secondary.insertEquationData(interpolator.getInterpolatedSecondaryState(index), yTmp); + ++index; + } + + // evaluate a first estimate of the derivative (first E in the PECE sequence) + computeDerivatives(stepEnd, yTmp, yDot); + + // update Nordsieck vector + for (int j = 0; j < y0.length; ++j) { + predictedScaled[j] = stepSize * yDot[j]; + } + nordsieckTmp = updateHighOrderDerivativesPhase1(nordsieck); + updateHighOrderDerivativesPhase2(scaled, predictedScaled, nordsieckTmp); + + // apply correction (C in the PECE sequence) + error = nordsieckTmp.walkInOptimizedOrder(new Corrector(y, predictedScaled, yTmp)); + + if (error >= 1.0) { + // reject the step and attempt to reduce error by stepsize control + final double factor = computeStepGrowShrinkFactor(error); + hNew = filterStep(stepSize * factor, forward, false); + interpolator.rescale(hNew); + } + } + + // evaluate a final estimate of the derivative (second E in the PECE sequence) + final double stepEnd = stepStart + stepSize; + computeDerivatives(stepEnd, yTmp, yDot); + + // update Nordsieck vector + final double[] correctedScaled = new double[y0.length]; + for (int j = 0; j < y0.length; ++j) { + correctedScaled[j] = stepSize * yDot[j]; + } + updateHighOrderDerivativesPhase2(predictedScaled, correctedScaled, nordsieckTmp); + + // discrete events handling + System.arraycopy(yTmp, 0, y, 0, y.length); + interpolator.reinitialize(stepEnd, stepSize, correctedScaled, nordsieckTmp); + interpolator.storeTime(stepStart); + interpolator.shift(); + interpolator.storeTime(stepEnd); + stepStart = acceptStep(interpolator, y, yDot, t); + scaled = correctedScaled; + nordsieck = nordsieckTmp; + + if (!isLastStep) { + + // prepare next step + interpolator.storeTime(stepStart); + + if (resetOccurred) { + // some events handler has triggered changes that + // invalidate the derivatives, we need to restart from scratch + start(stepStart, y, t); + interpolator.reinitialize(stepStart, stepSize, scaled, nordsieck); + + } + + // stepsize control for next step + final double factor = computeStepGrowShrinkFactor(error); + final double scaledH = stepSize * factor; + final double nextT = stepStart + scaledH; + final boolean nextIsLast = forward ? (nextT >= t) : (nextT <= t); + hNew = filterStep(scaledH, forward, nextIsLast); + + final double filteredNextT = stepStart + hNew; + final boolean filteredNextIsLast = forward ? (filteredNextT >= t) : (filteredNextT <= t); + if (filteredNextIsLast) { + hNew = t - stepStart; + } + + interpolator.rescale(hNew); + } + + } while (!isLastStep); + + // dispatch results + equations.setTime(stepStart); + equations.setCompleteState(y); + + resetInternalState(); + + } + + /** Corrector for current state in Adams-Moulton method. + * <p> + * This visitor implements the Taylor series formula: + * <pre> + * Y<sub>n+1</sub> = y<sub>n</sub> + s<sub>1</sub>(n+1) + [ -1 +1 -1 +1 ... ±1 ] r<sub>n+1</sub> + * </pre> + * </p> + */ + private class Corrector implements RealMatrixPreservingVisitor { + + /** Previous state. */ + private final double[] previous; + + /** Current scaled first derivative. */ + private final double[] scaled; + + /** Current state before correction. */ + private final double[] before; + + /** Current state after correction. */ + private final double[] after; + + /** Simple constructor. + * @param previous previous state + * @param scaled current scaled first derivative + * @param state state to correct (will be overwritten after visit) + */ + Corrector(final double[] previous, final double[] scaled, final double[] state) { + this.previous = previous; + this.scaled = scaled; + this.after = state; + this.before = state.clone(); + } + + /** {@inheritDoc} */ + public void start(int rows, int columns, + int startRow, int endRow, int startColumn, int endColumn) { + Arrays.fill(after, 0.0); + } + + /** {@inheritDoc} */ + public void visit(int row, int column, double value) { + if ((row & 0x1) == 0) { + after[column] -= value; + } else { + after[column] += value; + } + } + + /** + * End visiting the Nordsieck vector. + * <p>The correction is used to control stepsize. So its amplitude is + * considered to be an error, which must be normalized according to + * error control settings. If the normalized value is greater than 1, + * the correction was too large and the step must be rejected.</p> + * @return the normalized correction, if greater than 1, the step + * must be rejected + */ + public double end() { + + double error = 0; + for (int i = 0; i < after.length; ++i) { + after[i] += previous[i] + scaled[i]; + if (i < mainSetDimension) { + final double yScale = FastMath.max(FastMath.abs(previous[i]), FastMath.abs(after[i])); + final double tol = (vecAbsoluteTolerance == null) ? + (scalAbsoluteTolerance + scalRelativeTolerance * yScale) : + (vecAbsoluteTolerance[i] + vecRelativeTolerance[i] * yScale); + final double ratio = (after[i] - before[i]) / tol; // (corrected-predicted)/tol + error += ratio * ratio; + } + } + + return FastMath.sqrt(error / mainSetDimension); + + } + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsNordsieckFieldTransformer.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsNordsieckFieldTransformer.java new file mode 100644 index 0000000..b8f872b --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsNordsieckFieldTransformer.java @@ -0,0 +1,363 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.math3.Field; +import org.apache.commons.math3.RealFieldElement; +import org.apache.commons.math3.linear.Array2DRowFieldMatrix; +import org.apache.commons.math3.linear.ArrayFieldVector; +import org.apache.commons.math3.linear.FieldDecompositionSolver; +import org.apache.commons.math3.linear.FieldLUDecomposition; +import org.apache.commons.math3.linear.FieldMatrix; +import org.apache.commons.math3.util.MathArrays; + +/** Transformer to Nordsieck vectors for Adams integrators. + * <p>This class is used by {@link AdamsBashforthIntegrator Adams-Bashforth} and + * {@link AdamsMoultonIntegrator Adams-Moulton} integrators to convert between + * classical representation with several previous first derivatives and Nordsieck + * representation with higher order scaled derivatives.</p> + * + * <p>We define scaled derivatives s<sub>i</sub>(n) at step n as: + * <pre> + * s<sub>1</sub>(n) = h y'<sub>n</sub> for first derivative + * s<sub>2</sub>(n) = h<sup>2</sup>/2 y''<sub>n</sub> for second derivative + * s<sub>3</sub>(n) = h<sup>3</sup>/6 y'''<sub>n</sub> for third derivative + * ... + * s<sub>k</sub>(n) = h<sup>k</sup>/k! y<sup>(k)</sup><sub>n</sub> for k<sup>th</sup> derivative + * </pre></p> + * + * <p>With the previous definition, the classical representation of multistep methods + * uses first derivatives only, i.e. it handles y<sub>n</sub>, s<sub>1</sub>(n) and + * q<sub>n</sub> where q<sub>n</sub> is defined as: + * <pre> + * q<sub>n</sub> = [ s<sub>1</sub>(n-1) s<sub>1</sub>(n-2) ... s<sub>1</sub>(n-(k-1)) ]<sup>T</sup> + * </pre> + * (we omit the k index in the notation for clarity).</p> + * + * <p>Another possible representation uses the Nordsieck vector with + * higher degrees scaled derivatives all taken at the same step, i.e it handles y<sub>n</sub>, + * s<sub>1</sub>(n) and r<sub>n</sub>) where r<sub>n</sub> is defined as: + * <pre> + * r<sub>n</sub> = [ s<sub>2</sub>(n), s<sub>3</sub>(n) ... s<sub>k</sub>(n) ]<sup>T</sup> + * </pre> + * (here again we omit the k index in the notation for clarity) + * </p> + * + * <p>Taylor series formulas show that for any index offset i, s<sub>1</sub>(n-i) can be + * computed from s<sub>1</sub>(n), s<sub>2</sub>(n) ... s<sub>k</sub>(n), the formula being exact + * for degree k polynomials. + * <pre> + * s<sub>1</sub>(n-i) = s<sub>1</sub>(n) + ∑<sub>j>0</sub> (j+1) (-i)<sup>j</sup> s<sub>j+1</sub>(n) + * </pre> + * The previous formula can be used with several values for i to compute the transform between + * classical representation and Nordsieck vector at step end. The transform between r<sub>n</sub> + * and q<sub>n</sub> resulting from the Taylor series formulas above is: + * <pre> + * q<sub>n</sub> = s<sub>1</sub>(n) u + P r<sub>n</sub> + * </pre> + * where u is the [ 1 1 ... 1 ]<sup>T</sup> vector and P is the (k-1)×(k-1) matrix built + * with the (j+1) (-i)<sup>j</sup> terms with i being the row number starting from 1 and j being + * the column number starting from 1: + * <pre> + * [ -2 3 -4 5 ... ] + * [ -4 12 -32 80 ... ] + * P = [ -6 27 -108 405 ... ] + * [ -8 48 -256 1280 ... ] + * [ ... ] + * </pre></p> + * + * <p>Changing -i into +i in the formula above can be used to compute a similar transform between + * classical representation and Nordsieck vector at step start. The resulting matrix is simply + * the absolute value of matrix P.</p> + * + * <p>For {@link AdamsBashforthIntegrator Adams-Bashforth} method, the Nordsieck vector + * at step n+1 is computed from the Nordsieck vector at step n as follows: + * <ul> + * <li>y<sub>n+1</sub> = y<sub>n</sub> + s<sub>1</sub>(n) + u<sup>T</sup> r<sub>n</sub></li> + * <li>s<sub>1</sub>(n+1) = h f(t<sub>n+1</sub>, y<sub>n+1</sub>)</li> + * <li>r<sub>n+1</sub> = (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub></li> + * </ul> + * where A is a rows shifting matrix (the lower left part is an identity matrix): + * <pre> + * [ 0 0 ... 0 0 | 0 ] + * [ ---------------+---] + * [ 1 0 ... 0 0 | 0 ] + * A = [ 0 1 ... 0 0 | 0 ] + * [ ... | 0 ] + * [ 0 0 ... 1 0 | 0 ] + * [ 0 0 ... 0 1 | 0 ] + * </pre></p> + * + * <p>For {@link AdamsMoultonIntegrator Adams-Moulton} method, the predicted Nordsieck vector + * at step n+1 is computed from the Nordsieck vector at step n as follows: + * <ul> + * <li>Y<sub>n+1</sub> = y<sub>n</sub> + s<sub>1</sub>(n) + u<sup>T</sup> r<sub>n</sub></li> + * <li>S<sub>1</sub>(n+1) = h f(t<sub>n+1</sub>, Y<sub>n+1</sub>)</li> + * <li>R<sub>n+1</sub> = (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub></li> + * </ul> + * From this predicted vector, the corrected vector is computed as follows: + * <ul> + * <li>y<sub>n+1</sub> = y<sub>n</sub> + S<sub>1</sub>(n+1) + [ -1 +1 -1 +1 ... ±1 ] r<sub>n+1</sub></li> + * <li>s<sub>1</sub>(n+1) = h f(t<sub>n+1</sub>, y<sub>n+1</sub>)</li> + * <li>r<sub>n+1</sub> = R<sub>n+1</sub> + (s<sub>1</sub>(n+1) - S<sub>1</sub>(n+1)) P<sup>-1</sup> u</li> + * </ul> + * where the upper case Y<sub>n+1</sub>, S<sub>1</sub>(n+1) and R<sub>n+1</sub> represent the + * predicted states whereas the lower case y<sub>n+1</sub>, s<sub>n+1</sub> and r<sub>n+1</sub> + * represent the corrected states.</p> + * + * <p>We observe that both methods use similar update formulas. In both cases a P<sup>-1</sup>u + * vector and a P<sup>-1</sup> A P matrix are used that do not depend on the state, + * they only depend on k. This class handles these transformations.</p> + * + * @param <T> the type of the field elements + * @since 3.6 + */ +public class AdamsNordsieckFieldTransformer<T extends RealFieldElement<T>> { + + /** Cache for already computed coefficients. */ + private static final Map<Integer, + Map<Field<? extends RealFieldElement<?>>, + AdamsNordsieckFieldTransformer<? extends RealFieldElement<?>>>> CACHE = + new HashMap<Integer, Map<Field<? extends RealFieldElement<?>>, + AdamsNordsieckFieldTransformer<? extends RealFieldElement<?>>>>(); + + /** Field to which the time and state vector elements belong. */ + private final Field<T> field; + + /** Update matrix for the higher order derivatives h<sup>2</sup>/2 y'', h<sup>3</sup>/6 y''' ... */ + private final Array2DRowFieldMatrix<T> update; + + /** Update coefficients of the higher order derivatives wrt y'. */ + private final T[] c1; + + /** Simple constructor. + * @param field field to which the time and state vector elements belong + * @param n number of steps of the multistep method + * (excluding the one being computed) + */ + private AdamsNordsieckFieldTransformer(final Field<T> field, final int n) { + + this.field = field; + final int rows = n - 1; + + // compute coefficients + FieldMatrix<T> bigP = buildP(rows); + FieldDecompositionSolver<T> pSolver = + new FieldLUDecomposition<T>(bigP).getSolver(); + + T[] u = MathArrays.buildArray(field, rows); + Arrays.fill(u, field.getOne()); + c1 = pSolver.solve(new ArrayFieldVector<T>(u, false)).toArray(); + + // update coefficients are computed by combining transform from + // Nordsieck to multistep, then shifting rows to represent step advance + // then applying inverse transform + T[][] shiftedP = bigP.getData(); + for (int i = shiftedP.length - 1; i > 0; --i) { + // shift rows + shiftedP[i] = shiftedP[i - 1]; + } + shiftedP[0] = MathArrays.buildArray(field, rows); + Arrays.fill(shiftedP[0], field.getZero()); + update = new Array2DRowFieldMatrix<T>(pSolver.solve(new Array2DRowFieldMatrix<T>(shiftedP, false)).getData()); + + } + + /** Get the Nordsieck transformer for a given field and number of steps. + * @param field field to which the time and state vector elements belong + * @param nSteps number of steps of the multistep method + * (excluding the one being computed) + * @return Nordsieck transformer for the specified field and number of steps + * @param <T> the type of the field elements + */ + @SuppressWarnings("unchecked") + public static <T extends RealFieldElement<T>> AdamsNordsieckFieldTransformer<T> + getInstance(final Field<T> field, final int nSteps) { + synchronized(CACHE) { + Map<Field<? extends RealFieldElement<?>>, + AdamsNordsieckFieldTransformer<? extends RealFieldElement<?>>> map = CACHE.get(nSteps); + if (map == null) { + map = new HashMap<Field<? extends RealFieldElement<?>>, + AdamsNordsieckFieldTransformer<? extends RealFieldElement<?>>>(); + CACHE.put(nSteps, map); + } + @SuppressWarnings("rawtypes") // use rawtype to avoid compilation problems with java 1.5 + AdamsNordsieckFieldTransformer t = map.get(field); + if (t == null) { + t = new AdamsNordsieckFieldTransformer<T>(field, nSteps); + map.put(field, (AdamsNordsieckFieldTransformer<T>) t); + } + return (AdamsNordsieckFieldTransformer<T>) t; + + } + } + + /** Build the P matrix. + * <p>The P matrix general terms are shifted (j+1) (-i)<sup>j</sup> terms + * with i being the row number starting from 1 and j being the column + * number starting from 1: + * <pre> + * [ -2 3 -4 5 ... ] + * [ -4 12 -32 80 ... ] + * P = [ -6 27 -108 405 ... ] + * [ -8 48 -256 1280 ... ] + * [ ... ] + * </pre></p> + * @param rows number of rows of the matrix + * @return P matrix + */ + private FieldMatrix<T> buildP(final int rows) { + + final T[][] pData = MathArrays.buildArray(field, rows, rows); + + for (int i = 1; i <= pData.length; ++i) { + // build the P matrix elements from Taylor series formulas + final T[] pI = pData[i - 1]; + final int factor = -i; + T aj = field.getZero().add(factor); + for (int j = 1; j <= pI.length; ++j) { + pI[j - 1] = aj.multiply(j + 1); + aj = aj.multiply(factor); + } + } + + return new Array2DRowFieldMatrix<T>(pData, false); + + } + + /** Initialize the high order scaled derivatives at step start. + * @param h step size to use for scaling + * @param t first steps times + * @param y first steps states + * @param yDot first steps derivatives + * @return Nordieck vector at start of first step (h<sup>2</sup>/2 y''<sub>n</sub>, + * h<sup>3</sup>/6 y'''<sub>n</sub> ... h<sup>k</sup>/k! y<sup>(k)</sup><sub>n</sub>) + */ + + public Array2DRowFieldMatrix<T> initializeHighOrderDerivatives(final T h, final T[] t, + final T[][] y, + final T[][] yDot) { + + // using Taylor series with di = ti - t0, we get: + // y(ti) - y(t0) - di y'(t0) = di^2 / h^2 s2 + ... + di^k / h^k sk + O(h^k) + // y'(ti) - y'(t0) = 2 di / h^2 s2 + ... + k di^(k-1) / h^k sk + O(h^(k-1)) + // we write these relations for i = 1 to i= 1+n/2 as a set of n + 2 linear + // equations depending on the Nordsieck vector [s2 ... sk rk], so s2 to sk correspond + // to the appropriately truncated Taylor expansion, and rk is the Taylor remainder. + // The goal is to have s2 to sk as accurate as possible considering the fact the sum is + // truncated and we don't want the error terms to be included in s2 ... sk, so we need + // to solve also for the remainder + final T[][] a = MathArrays.buildArray(field, c1.length + 1, c1.length + 1); + final T[][] b = MathArrays.buildArray(field, c1.length + 1, y[0].length); + final T[] y0 = y[0]; + final T[] yDot0 = yDot[0]; + for (int i = 1; i < y.length; ++i) { + + final T di = t[i].subtract(t[0]); + final T ratio = di.divide(h); + T dikM1Ohk = h.reciprocal(); + + // linear coefficients of equations + // y(ti) - y(t0) - di y'(t0) and y'(ti) - y'(t0) + final T[] aI = a[2 * i - 2]; + final T[] aDotI = (2 * i - 1) < a.length ? a[2 * i - 1] : null; + for (int j = 0; j < aI.length; ++j) { + dikM1Ohk = dikM1Ohk.multiply(ratio); + aI[j] = di.multiply(dikM1Ohk); + if (aDotI != null) { + aDotI[j] = dikM1Ohk.multiply(j + 2); + } + } + + // expected value of the previous equations + final T[] yI = y[i]; + final T[] yDotI = yDot[i]; + final T[] bI = b[2 * i - 2]; + final T[] bDotI = (2 * i - 1) < b.length ? b[2 * i - 1] : null; + for (int j = 0; j < yI.length; ++j) { + bI[j] = yI[j].subtract(y0[j]).subtract(di.multiply(yDot0[j])); + if (bDotI != null) { + bDotI[j] = yDotI[j].subtract(yDot0[j]); + } + } + + } + + // solve the linear system to get the best estimate of the Nordsieck vector [s2 ... sk], + // with the additional terms s(k+1) and c grabbing the parts after the truncated Taylor expansion + final FieldLUDecomposition<T> decomposition = new FieldLUDecomposition<T>(new Array2DRowFieldMatrix<T>(a, false)); + final FieldMatrix<T> x = decomposition.getSolver().solve(new Array2DRowFieldMatrix<T>(b, false)); + + // extract just the Nordsieck vector [s2 ... sk] + final Array2DRowFieldMatrix<T> truncatedX = + new Array2DRowFieldMatrix<T>(field, x.getRowDimension() - 1, x.getColumnDimension()); + for (int i = 0; i < truncatedX.getRowDimension(); ++i) { + for (int j = 0; j < truncatedX.getColumnDimension(); ++j) { + truncatedX.setEntry(i, j, x.getEntry(i, j)); + } + } + return truncatedX; + + } + + /** Update the high order scaled derivatives for Adams integrators (phase 1). + * <p>The complete update of high order derivatives has a form similar to: + * <pre> + * r<sub>n+1</sub> = (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub> + * </pre> + * this method computes the P<sup>-1</sup> A P r<sub>n</sub> part.</p> + * @param highOrder high order scaled derivatives + * (h<sup>2</sup>/2 y'', ... h<sup>k</sup>/k! y(k)) + * @return updated high order derivatives + * @see #updateHighOrderDerivativesPhase2(RealFieldElement[], RealFieldElement[], Array2DRowFieldMatrix) + */ + public Array2DRowFieldMatrix<T> updateHighOrderDerivativesPhase1(final Array2DRowFieldMatrix<T> highOrder) { + return update.multiply(highOrder); + } + + /** Update the high order scaled derivatives Adams integrators (phase 2). + * <p>The complete update of high order derivatives has a form similar to: + * <pre> + * r<sub>n+1</sub> = (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub> + * </pre> + * this method computes the (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u part.</p> + * <p>Phase 1 of the update must already have been performed.</p> + * @param start first order scaled derivatives at step start + * @param end first order scaled derivatives at step end + * @param highOrder high order scaled derivatives, will be modified + * (h<sup>2</sup>/2 y'', ... h<sup>k</sup>/k! y(k)) + * @see #updateHighOrderDerivativesPhase1(Array2DRowFieldMatrix) + */ + public void updateHighOrderDerivativesPhase2(final T[] start, + final T[] end, + final Array2DRowFieldMatrix<T> highOrder) { + final T[][] data = highOrder.getDataRef(); + for (int i = 0; i < data.length; ++i) { + final T[] dataI = data[i]; + final T c1I = c1[i]; + for (int j = 0; j < dataI.length; ++j) { + dataI[j] = dataI[j].add(c1I.multiply(start[j].subtract(end[j]))); + } + } + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsNordsieckTransformer.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsNordsieckTransformer.java new file mode 100644 index 0000000..1c54b17 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsNordsieckTransformer.java @@ -0,0 +1,361 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.math3.fraction.BigFraction; +import org.apache.commons.math3.linear.Array2DRowFieldMatrix; +import org.apache.commons.math3.linear.Array2DRowRealMatrix; +import org.apache.commons.math3.linear.ArrayFieldVector; +import org.apache.commons.math3.linear.FieldDecompositionSolver; +import org.apache.commons.math3.linear.FieldLUDecomposition; +import org.apache.commons.math3.linear.FieldMatrix; +import org.apache.commons.math3.linear.MatrixUtils; +import org.apache.commons.math3.linear.QRDecomposition; +import org.apache.commons.math3.linear.RealMatrix; + +/** Transformer to Nordsieck vectors for Adams integrators. + * <p>This class is used by {@link AdamsBashforthIntegrator Adams-Bashforth} and + * {@link AdamsMoultonIntegrator Adams-Moulton} integrators to convert between + * classical representation with several previous first derivatives and Nordsieck + * representation with higher order scaled derivatives.</p> + * + * <p>We define scaled derivatives s<sub>i</sub>(n) at step n as: + * <pre> + * s<sub>1</sub>(n) = h y'<sub>n</sub> for first derivative + * s<sub>2</sub>(n) = h<sup>2</sup>/2 y''<sub>n</sub> for second derivative + * s<sub>3</sub>(n) = h<sup>3</sup>/6 y'''<sub>n</sub> for third derivative + * ... + * s<sub>k</sub>(n) = h<sup>k</sup>/k! y<sup>(k)</sup><sub>n</sub> for k<sup>th</sup> derivative + * </pre></p> + * + * <p>With the previous definition, the classical representation of multistep methods + * uses first derivatives only, i.e. it handles y<sub>n</sub>, s<sub>1</sub>(n) and + * q<sub>n</sub> where q<sub>n</sub> is defined as: + * <pre> + * q<sub>n</sub> = [ s<sub>1</sub>(n-1) s<sub>1</sub>(n-2) ... s<sub>1</sub>(n-(k-1)) ]<sup>T</sup> + * </pre> + * (we omit the k index in the notation for clarity).</p> + * + * <p>Another possible representation uses the Nordsieck vector with + * higher degrees scaled derivatives all taken at the same step, i.e it handles y<sub>n</sub>, + * s<sub>1</sub>(n) and r<sub>n</sub>) where r<sub>n</sub> is defined as: + * <pre> + * r<sub>n</sub> = [ s<sub>2</sub>(n), s<sub>3</sub>(n) ... s<sub>k</sub>(n) ]<sup>T</sup> + * </pre> + * (here again we omit the k index in the notation for clarity) + * </p> + * + * <p>Taylor series formulas show that for any index offset i, s<sub>1</sub>(n-i) can be + * computed from s<sub>1</sub>(n), s<sub>2</sub>(n) ... s<sub>k</sub>(n), the formula being exact + * for degree k polynomials. + * <pre> + * s<sub>1</sub>(n-i) = s<sub>1</sub>(n) + ∑<sub>j>0</sub> (j+1) (-i)<sup>j</sup> s<sub>j+1</sub>(n) + * </pre> + * The previous formula can be used with several values for i to compute the transform between + * classical representation and Nordsieck vector at step end. The transform between r<sub>n</sub> + * and q<sub>n</sub> resulting from the Taylor series formulas above is: + * <pre> + * q<sub>n</sub> = s<sub>1</sub>(n) u + P r<sub>n</sub> + * </pre> + * where u is the [ 1 1 ... 1 ]<sup>T</sup> vector and P is the (k-1)×(k-1) matrix built + * with the (j+1) (-i)<sup>j</sup> terms with i being the row number starting from 1 and j being + * the column number starting from 1: + * <pre> + * [ -2 3 -4 5 ... ] + * [ -4 12 -32 80 ... ] + * P = [ -6 27 -108 405 ... ] + * [ -8 48 -256 1280 ... ] + * [ ... ] + * </pre></p> + * + * <p>Changing -i into +i in the formula above can be used to compute a similar transform between + * classical representation and Nordsieck vector at step start. The resulting matrix is simply + * the absolute value of matrix P.</p> + * + * <p>For {@link AdamsBashforthIntegrator Adams-Bashforth} method, the Nordsieck vector + * at step n+1 is computed from the Nordsieck vector at step n as follows: + * <ul> + * <li>y<sub>n+1</sub> = y<sub>n</sub> + s<sub>1</sub>(n) + u<sup>T</sup> r<sub>n</sub></li> + * <li>s<sub>1</sub>(n+1) = h f(t<sub>n+1</sub>, y<sub>n+1</sub>)</li> + * <li>r<sub>n+1</sub> = (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub></li> + * </ul> + * where A is a rows shifting matrix (the lower left part is an identity matrix): + * <pre> + * [ 0 0 ... 0 0 | 0 ] + * [ ---------------+---] + * [ 1 0 ... 0 0 | 0 ] + * A = [ 0 1 ... 0 0 | 0 ] + * [ ... | 0 ] + * [ 0 0 ... 1 0 | 0 ] + * [ 0 0 ... 0 1 | 0 ] + * </pre></p> + * + * <p>For {@link AdamsMoultonIntegrator Adams-Moulton} method, the predicted Nordsieck vector + * at step n+1 is computed from the Nordsieck vector at step n as follows: + * <ul> + * <li>Y<sub>n+1</sub> = y<sub>n</sub> + s<sub>1</sub>(n) + u<sup>T</sup> r<sub>n</sub></li> + * <li>S<sub>1</sub>(n+1) = h f(t<sub>n+1</sub>, Y<sub>n+1</sub>)</li> + * <li>R<sub>n+1</sub> = (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub></li> + * </ul> + * From this predicted vector, the corrected vector is computed as follows: + * <ul> + * <li>y<sub>n+1</sub> = y<sub>n</sub> + S<sub>1</sub>(n+1) + [ -1 +1 -1 +1 ... ±1 ] r<sub>n+1</sub></li> + * <li>s<sub>1</sub>(n+1) = h f(t<sub>n+1</sub>, y<sub>n+1</sub>)</li> + * <li>r<sub>n+1</sub> = R<sub>n+1</sub> + (s<sub>1</sub>(n+1) - S<sub>1</sub>(n+1)) P<sup>-1</sup> u</li> + * </ul> + * where the upper case Y<sub>n+1</sub>, S<sub>1</sub>(n+1) and R<sub>n+1</sub> represent the + * predicted states whereas the lower case y<sub>n+1</sub>, s<sub>n+1</sub> and r<sub>n+1</sub> + * represent the corrected states.</p> + * + * <p>We observe that both methods use similar update formulas. In both cases a P<sup>-1</sup>u + * vector and a P<sup>-1</sup> A P matrix are used that do not depend on the state, + * they only depend on k. This class handles these transformations.</p> + * + * @since 2.0 + */ +public class AdamsNordsieckTransformer { + + /** Cache for already computed coefficients. */ + private static final Map<Integer, AdamsNordsieckTransformer> CACHE = + new HashMap<Integer, AdamsNordsieckTransformer>(); + + /** Update matrix for the higher order derivatives h<sup>2</sup>/2 y'', h<sup>3</sup>/6 y''' ... */ + private final Array2DRowRealMatrix update; + + /** Update coefficients of the higher order derivatives wrt y'. */ + private final double[] c1; + + /** Simple constructor. + * @param n number of steps of the multistep method + * (excluding the one being computed) + */ + private AdamsNordsieckTransformer(final int n) { + + final int rows = n - 1; + + // compute exact coefficients + FieldMatrix<BigFraction> bigP = buildP(rows); + FieldDecompositionSolver<BigFraction> pSolver = + new FieldLUDecomposition<BigFraction>(bigP).getSolver(); + + BigFraction[] u = new BigFraction[rows]; + Arrays.fill(u, BigFraction.ONE); + BigFraction[] bigC1 = pSolver.solve(new ArrayFieldVector<BigFraction>(u, false)).toArray(); + + // update coefficients are computed by combining transform from + // Nordsieck to multistep, then shifting rows to represent step advance + // then applying inverse transform + BigFraction[][] shiftedP = bigP.getData(); + for (int i = shiftedP.length - 1; i > 0; --i) { + // shift rows + shiftedP[i] = shiftedP[i - 1]; + } + shiftedP[0] = new BigFraction[rows]; + Arrays.fill(shiftedP[0], BigFraction.ZERO); + FieldMatrix<BigFraction> bigMSupdate = + pSolver.solve(new Array2DRowFieldMatrix<BigFraction>(shiftedP, false)); + + // convert coefficients to double + update = MatrixUtils.bigFractionMatrixToRealMatrix(bigMSupdate); + c1 = new double[rows]; + for (int i = 0; i < rows; ++i) { + c1[i] = bigC1[i].doubleValue(); + } + + } + + /** Get the Nordsieck transformer for a given number of steps. + * @param nSteps number of steps of the multistep method + * (excluding the one being computed) + * @return Nordsieck transformer for the specified number of steps + */ + public static AdamsNordsieckTransformer getInstance(final int nSteps) { + synchronized(CACHE) { + AdamsNordsieckTransformer t = CACHE.get(nSteps); + if (t == null) { + t = new AdamsNordsieckTransformer(nSteps); + CACHE.put(nSteps, t); + } + return t; + } + } + + /** Get the number of steps of the method + * (excluding the one being computed). + * @return number of steps of the method + * (excluding the one being computed) + * @deprecated as of 3.6, this method is not used anymore + */ + @Deprecated + public int getNSteps() { + return c1.length; + } + + /** Build the P matrix. + * <p>The P matrix general terms are shifted (j+1) (-i)<sup>j</sup> terms + * with i being the row number starting from 1 and j being the column + * number starting from 1: + * <pre> + * [ -2 3 -4 5 ... ] + * [ -4 12 -32 80 ... ] + * P = [ -6 27 -108 405 ... ] + * [ -8 48 -256 1280 ... ] + * [ ... ] + * </pre></p> + * @param rows number of rows of the matrix + * @return P matrix + */ + private FieldMatrix<BigFraction> buildP(final int rows) { + + final BigFraction[][] pData = new BigFraction[rows][rows]; + + for (int i = 1; i <= pData.length; ++i) { + // build the P matrix elements from Taylor series formulas + final BigFraction[] pI = pData[i - 1]; + final int factor = -i; + int aj = factor; + for (int j = 1; j <= pI.length; ++j) { + pI[j - 1] = new BigFraction(aj * (j + 1)); + aj *= factor; + } + } + + return new Array2DRowFieldMatrix<BigFraction>(pData, false); + + } + + /** Initialize the high order scaled derivatives at step start. + * @param h step size to use for scaling + * @param t first steps times + * @param y first steps states + * @param yDot first steps derivatives + * @return Nordieck vector at start of first step (h<sup>2</sup>/2 y''<sub>n</sub>, + * h<sup>3</sup>/6 y'''<sub>n</sub> ... h<sup>k</sup>/k! y<sup>(k)</sup><sub>n</sub>) + */ + + public Array2DRowRealMatrix initializeHighOrderDerivatives(final double h, final double[] t, + final double[][] y, + final double[][] yDot) { + + // using Taylor series with di = ti - t0, we get: + // y(ti) - y(t0) - di y'(t0) = di^2 / h^2 s2 + ... + di^k / h^k sk + O(h^k) + // y'(ti) - y'(t0) = 2 di / h^2 s2 + ... + k di^(k-1) / h^k sk + O(h^(k-1)) + // we write these relations for i = 1 to i= 1+n/2 as a set of n + 2 linear + // equations depending on the Nordsieck vector [s2 ... sk rk], so s2 to sk correspond + // to the appropriately truncated Taylor expansion, and rk is the Taylor remainder. + // The goal is to have s2 to sk as accurate as possible considering the fact the sum is + // truncated and we don't want the error terms to be included in s2 ... sk, so we need + // to solve also for the remainder + final double[][] a = new double[c1.length + 1][c1.length + 1]; + final double[][] b = new double[c1.length + 1][y[0].length]; + final double[] y0 = y[0]; + final double[] yDot0 = yDot[0]; + for (int i = 1; i < y.length; ++i) { + + final double di = t[i] - t[0]; + final double ratio = di / h; + double dikM1Ohk = 1 / h; + + // linear coefficients of equations + // y(ti) - y(t0) - di y'(t0) and y'(ti) - y'(t0) + final double[] aI = a[2 * i - 2]; + final double[] aDotI = (2 * i - 1) < a.length ? a[2 * i - 1] : null; + for (int j = 0; j < aI.length; ++j) { + dikM1Ohk *= ratio; + aI[j] = di * dikM1Ohk; + if (aDotI != null) { + aDotI[j] = (j + 2) * dikM1Ohk; + } + } + + // expected value of the previous equations + final double[] yI = y[i]; + final double[] yDotI = yDot[i]; + final double[] bI = b[2 * i - 2]; + final double[] bDotI = (2 * i - 1) < b.length ? b[2 * i - 1] : null; + for (int j = 0; j < yI.length; ++j) { + bI[j] = yI[j] - y0[j] - di * yDot0[j]; + if (bDotI != null) { + bDotI[j] = yDotI[j] - yDot0[j]; + } + } + + } + + // solve the linear system to get the best estimate of the Nordsieck vector [s2 ... sk], + // with the additional terms s(k+1) and c grabbing the parts after the truncated Taylor expansion + final QRDecomposition decomposition = new QRDecomposition(new Array2DRowRealMatrix(a, false)); + final RealMatrix x = decomposition.getSolver().solve(new Array2DRowRealMatrix(b, false)); + + // extract just the Nordsieck vector [s2 ... sk] + final Array2DRowRealMatrix truncatedX = new Array2DRowRealMatrix(x.getRowDimension() - 1, x.getColumnDimension()); + for (int i = 0; i < truncatedX.getRowDimension(); ++i) { + for (int j = 0; j < truncatedX.getColumnDimension(); ++j) { + truncatedX.setEntry(i, j, x.getEntry(i, j)); + } + } + return truncatedX; + + } + + /** Update the high order scaled derivatives for Adams integrators (phase 1). + * <p>The complete update of high order derivatives has a form similar to: + * <pre> + * r<sub>n+1</sub> = (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub> + * </pre> + * this method computes the P<sup>-1</sup> A P r<sub>n</sub> part.</p> + * @param highOrder high order scaled derivatives + * (h<sup>2</sup>/2 y'', ... h<sup>k</sup>/k! y(k)) + * @return updated high order derivatives + * @see #updateHighOrderDerivativesPhase2(double[], double[], Array2DRowRealMatrix) + */ + public Array2DRowRealMatrix updateHighOrderDerivativesPhase1(final Array2DRowRealMatrix highOrder) { + return update.multiply(highOrder); + } + + /** Update the high order scaled derivatives Adams integrators (phase 2). + * <p>The complete update of high order derivatives has a form similar to: + * <pre> + * r<sub>n+1</sub> = (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub> + * </pre> + * this method computes the (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u part.</p> + * <p>Phase 1 of the update must already have been performed.</p> + * @param start first order scaled derivatives at step start + * @param end first order scaled derivatives at step end + * @param highOrder high order scaled derivatives, will be modified + * (h<sup>2</sup>/2 y'', ... h<sup>k</sup>/k! y(k)) + * @see #updateHighOrderDerivativesPhase1(Array2DRowRealMatrix) + */ + public void updateHighOrderDerivativesPhase2(final double[] start, + final double[] end, + final Array2DRowRealMatrix highOrder) { + final double[][] data = highOrder.getDataRef(); + for (int i = 0; i < data.length; ++i) { + final double[] dataI = data[i]; + final double c1I = c1[i]; + for (int j = 0; j < dataI.length; ++j) { + dataI[j] += c1I * (start[j] - end[j]); + } + } + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/AdaptiveStepsizeFieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdaptiveStepsizeFieldIntegrator.java new file mode 100644 index 0000000..c8e592b --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdaptiveStepsizeFieldIntegrator.java @@ -0,0 +1,366 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.Field; +import org.apache.commons.math3.RealFieldElement; +import org.apache.commons.math3.exception.DimensionMismatchException; +import org.apache.commons.math3.exception.MaxCountExceededException; +import org.apache.commons.math3.exception.NumberIsTooSmallException; +import org.apache.commons.math3.exception.util.LocalizedFormats; +import org.apache.commons.math3.ode.AbstractFieldIntegrator; +import org.apache.commons.math3.ode.FieldEquationsMapper; +import org.apache.commons.math3.ode.FieldODEState; +import org.apache.commons.math3.ode.FieldODEStateAndDerivative; +import org.apache.commons.math3.util.FastMath; +import org.apache.commons.math3.util.MathArrays; +import org.apache.commons.math3.util.MathUtils; + +/** + * This abstract class holds the common part of all adaptive + * stepsize integrators for Ordinary Differential Equations. + * + * <p>These algorithms perform integration with stepsize control, which + * means the user does not specify the integration step but rather a + * tolerance on error. The error threshold is computed as + * <pre> + * threshold_i = absTol_i + relTol_i * max (abs (ym), abs (ym+1)) + * </pre> + * where absTol_i is the absolute tolerance for component i of the + * state vector and relTol_i is the relative tolerance for the same + * component. The user can also use only two scalar values absTol and + * relTol which will be used for all components. + * </p> + * <p> + * Note that <em>only</em> the {@link FieldODEState#getState() main part} + * of the state vector is used for stepsize control. The {@link + * FieldODEState#getSecondaryState(int) secondary parts} of the state + * vector are explicitly ignored for stepsize control. + * </p> + * + * <p>If the estimated error for ym+1 is such that + * <pre> + * sqrt((sum (errEst_i / threshold_i)^2 ) / n) < 1 + * </pre> + * + * (where n is the main set dimension) then the step is accepted, + * otherwise the step is rejected and a new attempt is made with a new + * stepsize.</p> + * + * @param <T> the type of the field elements + * @since 3.6 + * + */ + +public abstract class AdaptiveStepsizeFieldIntegrator<T extends RealFieldElement<T>> + extends AbstractFieldIntegrator<T> { + + /** Allowed absolute scalar error. */ + protected double scalAbsoluteTolerance; + + /** Allowed relative scalar error. */ + protected double scalRelativeTolerance; + + /** Allowed absolute vectorial error. */ + protected double[] vecAbsoluteTolerance; + + /** Allowed relative vectorial error. */ + protected double[] vecRelativeTolerance; + + /** Main set dimension. */ + protected int mainSetDimension; + + /** User supplied initial step. */ + private T initialStep; + + /** Minimal step. */ + private T minStep; + + /** Maximal step. */ + private T maxStep; + + /** Build an integrator with the given stepsize bounds. + * The default step handler does nothing. + * @param field field to which the time and state vector elements belong + * @param name name of the method + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + */ + public AdaptiveStepsizeFieldIntegrator(final Field<T> field, final String name, + final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) { + + super(field, name); + setStepSizeControl(minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance); + resetInternalState(); + + } + + /** Build an integrator with the given stepsize bounds. + * The default step handler does nothing. + * @param field field to which the time and state vector elements belong + * @param name name of the method + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + */ + public AdaptiveStepsizeFieldIntegrator(final Field<T> field, final String name, + final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) { + + super(field, name); + setStepSizeControl(minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance); + resetInternalState(); + + } + + /** Set the adaptive step size control parameters. + * <p> + * A side effect of this method is to also reset the initial + * step so it will be automatically computed by the integrator + * if {@link #setInitialStepSize(RealFieldElement) setInitialStepSize} + * is not called by the user. + * </p> + * @param minimalStep minimal step (must be positive even for backward + * integration), the last step can be smaller than this + * @param maximalStep maximal step (must be positive even for backward + * integration) + * @param absoluteTolerance allowed absolute error + * @param relativeTolerance allowed relative error + */ + public void setStepSizeControl(final double minimalStep, final double maximalStep, + final double absoluteTolerance, + final double relativeTolerance) { + + minStep = getField().getZero().add(FastMath.abs(minimalStep)); + maxStep = getField().getZero().add(FastMath.abs(maximalStep)); + initialStep = getField().getOne().negate(); + + scalAbsoluteTolerance = absoluteTolerance; + scalRelativeTolerance = relativeTolerance; + vecAbsoluteTolerance = null; + vecRelativeTolerance = null; + + } + + /** Set the adaptive step size control parameters. + * <p> + * A side effect of this method is to also reset the initial + * step so it will be automatically computed by the integrator + * if {@link #setInitialStepSize(RealFieldElement) setInitialStepSize} + * is not called by the user. + * </p> + * @param minimalStep minimal step (must be positive even for backward + * integration), the last step can be smaller than this + * @param maximalStep maximal step (must be positive even for backward + * integration) + * @param absoluteTolerance allowed absolute error + * @param relativeTolerance allowed relative error + */ + public void setStepSizeControl(final double minimalStep, final double maximalStep, + final double[] absoluteTolerance, + final double[] relativeTolerance) { + + minStep = getField().getZero().add(FastMath.abs(minimalStep)); + maxStep = getField().getZero().add(FastMath.abs(maximalStep)); + initialStep = getField().getOne().negate(); + + scalAbsoluteTolerance = 0; + scalRelativeTolerance = 0; + vecAbsoluteTolerance = absoluteTolerance.clone(); + vecRelativeTolerance = relativeTolerance.clone(); + + } + + /** Set the initial step size. + * <p>This method allows the user to specify an initial positive + * step size instead of letting the integrator guess it by + * itself. If this method is not called before integration is + * started, the initial step size will be estimated by the + * integrator.</p> + * @param initialStepSize initial step size to use (must be positive even + * for backward integration ; providing a negative value or a value + * outside of the min/max step interval will lead the integrator to + * ignore the value and compute the initial step size by itself) + */ + public void setInitialStepSize(final T initialStepSize) { + if (initialStepSize.subtract(minStep).getReal() < 0 || + initialStepSize.subtract(maxStep).getReal() > 0) { + initialStep = getField().getOne().negate(); + } else { + initialStep = initialStepSize; + } + } + + /** {@inheritDoc} */ + @Override + protected void sanityChecks(final FieldODEState<T> eqn, final T t) + throws DimensionMismatchException, NumberIsTooSmallException { + + super.sanityChecks(eqn, t); + + mainSetDimension = eqn.getStateDimension(); + + if (vecAbsoluteTolerance != null && vecAbsoluteTolerance.length != mainSetDimension) { + throw new DimensionMismatchException(mainSetDimension, vecAbsoluteTolerance.length); + } + + if (vecRelativeTolerance != null && vecRelativeTolerance.length != mainSetDimension) { + throw new DimensionMismatchException(mainSetDimension, vecRelativeTolerance.length); + } + + } + + /** Initialize the integration step. + * @param forward forward integration indicator + * @param order order of the method + * @param scale scaling vector for the state vector (can be shorter than state vector) + * @param state0 state at integration start time + * @param mapper mapper for all the equations + * @return first integration step + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + * @exception DimensionMismatchException if arrays dimensions do not match equations settings + */ + public T initializeStep(final boolean forward, final int order, final T[] scale, + final FieldODEStateAndDerivative<T> state0, + final FieldEquationsMapper<T> mapper) + throws MaxCountExceededException, DimensionMismatchException { + + if (initialStep.getReal() > 0) { + // use the user provided value + return forward ? initialStep : initialStep.negate(); + } + + // very rough first guess : h = 0.01 * ||y/scale|| / ||y'/scale|| + // this guess will be used to perform an Euler step + final T[] y0 = mapper.mapState(state0); + final T[] yDot0 = mapper.mapDerivative(state0); + T yOnScale2 = getField().getZero(); + T yDotOnScale2 = getField().getZero(); + for (int j = 0; j < scale.length; ++j) { + final T ratio = y0[j].divide(scale[j]); + yOnScale2 = yOnScale2.add(ratio.multiply(ratio)); + final T ratioDot = yDot0[j].divide(scale[j]); + yDotOnScale2 = yDotOnScale2.add(ratioDot.multiply(ratioDot)); + } + + T h = (yOnScale2.getReal() < 1.0e-10 || yDotOnScale2.getReal() < 1.0e-10) ? + getField().getZero().add(1.0e-6) : + yOnScale2.divide(yDotOnScale2).sqrt().multiply(0.01); + if (! forward) { + h = h.negate(); + } + + // perform an Euler step using the preceding rough guess + final T[] y1 = MathArrays.buildArray(getField(), y0.length); + for (int j = 0; j < y0.length; ++j) { + y1[j] = y0[j].add(yDot0[j].multiply(h)); + } + final T[] yDot1 = computeDerivatives(state0.getTime().add(h), y1); + + // estimate the second derivative of the solution + T yDDotOnScale = getField().getZero(); + for (int j = 0; j < scale.length; ++j) { + final T ratioDotDot = yDot1[j].subtract(yDot0[j]).divide(scale[j]); + yDDotOnScale = yDDotOnScale.add(ratioDotDot.multiply(ratioDotDot)); + } + yDDotOnScale = yDDotOnScale.sqrt().divide(h); + + // step size is computed such that + // h^order * max (||y'/tol||, ||y''/tol||) = 0.01 + final T maxInv2 = MathUtils.max(yDotOnScale2.sqrt(), yDDotOnScale); + final T h1 = maxInv2.getReal() < 1.0e-15 ? + MathUtils.max(getField().getZero().add(1.0e-6), h.abs().multiply(0.001)) : + maxInv2.multiply(100).reciprocal().pow(1.0 / order); + h = MathUtils.min(h.abs().multiply(100), h1); + h = MathUtils.max(h, state0.getTime().abs().multiply(1.0e-12)); // avoids cancellation when computing t1 - t0 + h = MathUtils.max(minStep, MathUtils.min(maxStep, h)); + if (! forward) { + h = h.negate(); + } + + return h; + + } + + /** Filter the integration step. + * @param h signed step + * @param forward forward integration indicator + * @param acceptSmall if true, steps smaller than the minimal value + * are silently increased up to this value, if false such small + * steps generate an exception + * @return a bounded integration step (h if no bound is reach, or a bounded value) + * @exception NumberIsTooSmallException if the step is too small and acceptSmall is false + */ + protected T filterStep(final T h, final boolean forward, final boolean acceptSmall) + throws NumberIsTooSmallException { + + T filteredH = h; + if (h.abs().subtract(minStep).getReal() < 0) { + if (acceptSmall) { + filteredH = forward ? minStep : minStep.negate(); + } else { + throw new NumberIsTooSmallException(LocalizedFormats.MINIMAL_STEPSIZE_REACHED_DURING_INTEGRATION, + h.abs().getReal(), minStep.getReal(), true); + } + } + + if (filteredH.subtract(maxStep).getReal() > 0) { + filteredH = maxStep; + } else if (filteredH.add(maxStep).getReal() < 0) { + filteredH = maxStep.negate(); + } + + return filteredH; + + } + + /** Reset internal state to dummy values. */ + protected void resetInternalState() { + setStepStart(null); + setStepSize(minStep.multiply(maxStep).sqrt()); + } + + /** Get the minimal step. + * @return minimal step + */ + public T getMinStep() { + return minStep; + } + + /** Get the maximal step. + * @return maximal step + */ + public T getMaxStep() { + return maxStep; + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/AdaptiveStepsizeIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdaptiveStepsizeIntegrator.java new file mode 100644 index 0000000..ab79bbf --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdaptiveStepsizeIntegrator.java @@ -0,0 +1,375 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.exception.DimensionMismatchException; +import org.apache.commons.math3.exception.MaxCountExceededException; +import org.apache.commons.math3.exception.NoBracketingException; +import org.apache.commons.math3.exception.NumberIsTooSmallException; +import org.apache.commons.math3.exception.util.LocalizedFormats; +import org.apache.commons.math3.ode.AbstractIntegrator; +import org.apache.commons.math3.ode.ExpandableStatefulODE; +import org.apache.commons.math3.util.FastMath; + +/** + * This abstract class holds the common part of all adaptive + * stepsize integrators for Ordinary Differential Equations. + * + * <p>These algorithms perform integration with stepsize control, which + * means the user does not specify the integration step but rather a + * tolerance on error. The error threshold is computed as + * <pre> + * threshold_i = absTol_i + relTol_i * max (abs (ym), abs (ym+1)) + * </pre> + * where absTol_i is the absolute tolerance for component i of the + * state vector and relTol_i is the relative tolerance for the same + * component. The user can also use only two scalar values absTol and + * relTol which will be used for all components. + * </p> + * <p> + * If the Ordinary Differential Equations is an {@link ExpandableStatefulODE + * extended ODE} rather than a {@link + * org.apache.commons.math3.ode.FirstOrderDifferentialEquations basic ODE}, then + * <em>only</em> the {@link ExpandableStatefulODE#getPrimaryState() primary part} + * of the state vector is used for stepsize control, not the complete state vector. + * </p> + * + * <p>If the estimated error for ym+1 is such that + * <pre> + * sqrt((sum (errEst_i / threshold_i)^2 ) / n) < 1 + * </pre> + * + * (where n is the main set dimension) then the step is accepted, + * otherwise the step is rejected and a new attempt is made with a new + * stepsize.</p> + * + * @since 1.2 + * + */ + +public abstract class AdaptiveStepsizeIntegrator + extends AbstractIntegrator { + + /** Allowed absolute scalar error. */ + protected double scalAbsoluteTolerance; + + /** Allowed relative scalar error. */ + protected double scalRelativeTolerance; + + /** Allowed absolute vectorial error. */ + protected double[] vecAbsoluteTolerance; + + /** Allowed relative vectorial error. */ + protected double[] vecRelativeTolerance; + + /** Main set dimension. */ + protected int mainSetDimension; + + /** User supplied initial step. */ + private double initialStep; + + /** Minimal step. */ + private double minStep; + + /** Maximal step. */ + private double maxStep; + + /** Build an integrator with the given stepsize bounds. + * The default step handler does nothing. + * @param name name of the method + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + */ + public AdaptiveStepsizeIntegrator(final String name, + final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) { + + super(name); + setStepSizeControl(minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance); + resetInternalState(); + + } + + /** Build an integrator with the given stepsize bounds. + * The default step handler does nothing. + * @param name name of the method + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + */ + public AdaptiveStepsizeIntegrator(final String name, + final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) { + + super(name); + setStepSizeControl(minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance); + resetInternalState(); + + } + + /** Set the adaptive step size control parameters. + * <p> + * A side effect of this method is to also reset the initial + * step so it will be automatically computed by the integrator + * if {@link #setInitialStepSize(double) setInitialStepSize} + * is not called by the user. + * </p> + * @param minimalStep minimal step (must be positive even for backward + * integration), the last step can be smaller than this + * @param maximalStep maximal step (must be positive even for backward + * integration) + * @param absoluteTolerance allowed absolute error + * @param relativeTolerance allowed relative error + */ + public void setStepSizeControl(final double minimalStep, final double maximalStep, + final double absoluteTolerance, + final double relativeTolerance) { + + minStep = FastMath.abs(minimalStep); + maxStep = FastMath.abs(maximalStep); + initialStep = -1; + + scalAbsoluteTolerance = absoluteTolerance; + scalRelativeTolerance = relativeTolerance; + vecAbsoluteTolerance = null; + vecRelativeTolerance = null; + + } + + /** Set the adaptive step size control parameters. + * <p> + * A side effect of this method is to also reset the initial + * step so it will be automatically computed by the integrator + * if {@link #setInitialStepSize(double) setInitialStepSize} + * is not called by the user. + * </p> + * @param minimalStep minimal step (must be positive even for backward + * integration), the last step can be smaller than this + * @param maximalStep maximal step (must be positive even for backward + * integration) + * @param absoluteTolerance allowed absolute error + * @param relativeTolerance allowed relative error + */ + public void setStepSizeControl(final double minimalStep, final double maximalStep, + final double[] absoluteTolerance, + final double[] relativeTolerance) { + + minStep = FastMath.abs(minimalStep); + maxStep = FastMath.abs(maximalStep); + initialStep = -1; + + scalAbsoluteTolerance = 0; + scalRelativeTolerance = 0; + vecAbsoluteTolerance = absoluteTolerance.clone(); + vecRelativeTolerance = relativeTolerance.clone(); + + } + + /** Set the initial step size. + * <p>This method allows the user to specify an initial positive + * step size instead of letting the integrator guess it by + * itself. If this method is not called before integration is + * started, the initial step size will be estimated by the + * integrator.</p> + * @param initialStepSize initial step size to use (must be positive even + * for backward integration ; providing a negative value or a value + * outside of the min/max step interval will lead the integrator to + * ignore the value and compute the initial step size by itself) + */ + public void setInitialStepSize(final double initialStepSize) { + if ((initialStepSize < minStep) || (initialStepSize > maxStep)) { + initialStep = -1.0; + } else { + initialStep = initialStepSize; + } + } + + /** {@inheritDoc} */ + @Override + protected void sanityChecks(final ExpandableStatefulODE equations, final double t) + throws DimensionMismatchException, NumberIsTooSmallException { + + super.sanityChecks(equations, t); + + mainSetDimension = equations.getPrimaryMapper().getDimension(); + + if ((vecAbsoluteTolerance != null) && (vecAbsoluteTolerance.length != mainSetDimension)) { + throw new DimensionMismatchException(mainSetDimension, vecAbsoluteTolerance.length); + } + + if ((vecRelativeTolerance != null) && (vecRelativeTolerance.length != mainSetDimension)) { + throw new DimensionMismatchException(mainSetDimension, vecRelativeTolerance.length); + } + + } + + /** Initialize the integration step. + * @param forward forward integration indicator + * @param order order of the method + * @param scale scaling vector for the state vector (can be shorter than state vector) + * @param t0 start time + * @param y0 state vector at t0 + * @param yDot0 first time derivative of y0 + * @param y1 work array for a state vector + * @param yDot1 work array for the first time derivative of y1 + * @return first integration step + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + * @exception DimensionMismatchException if arrays dimensions do not match equations settings + */ + public double initializeStep(final boolean forward, final int order, final double[] scale, + final double t0, final double[] y0, final double[] yDot0, + final double[] y1, final double[] yDot1) + throws MaxCountExceededException, DimensionMismatchException { + + if (initialStep > 0) { + // use the user provided value + return forward ? initialStep : -initialStep; + } + + // very rough first guess : h = 0.01 * ||y/scale|| / ||y'/scale|| + // this guess will be used to perform an Euler step + double ratio; + double yOnScale2 = 0; + double yDotOnScale2 = 0; + for (int j = 0; j < scale.length; ++j) { + ratio = y0[j] / scale[j]; + yOnScale2 += ratio * ratio; + ratio = yDot0[j] / scale[j]; + yDotOnScale2 += ratio * ratio; + } + + double h = ((yOnScale2 < 1.0e-10) || (yDotOnScale2 < 1.0e-10)) ? + 1.0e-6 : (0.01 * FastMath.sqrt(yOnScale2 / yDotOnScale2)); + if (! forward) { + h = -h; + } + + // perform an Euler step using the preceding rough guess + for (int j = 0; j < y0.length; ++j) { + y1[j] = y0[j] + h * yDot0[j]; + } + computeDerivatives(t0 + h, y1, yDot1); + + // estimate the second derivative of the solution + double yDDotOnScale = 0; + for (int j = 0; j < scale.length; ++j) { + ratio = (yDot1[j] - yDot0[j]) / scale[j]; + yDDotOnScale += ratio * ratio; + } + yDDotOnScale = FastMath.sqrt(yDDotOnScale) / h; + + // step size is computed such that + // h^order * max (||y'/tol||, ||y''/tol||) = 0.01 + final double maxInv2 = FastMath.max(FastMath.sqrt(yDotOnScale2), yDDotOnScale); + final double h1 = (maxInv2 < 1.0e-15) ? + FastMath.max(1.0e-6, 0.001 * FastMath.abs(h)) : + FastMath.pow(0.01 / maxInv2, 1.0 / order); + h = FastMath.min(100.0 * FastMath.abs(h), h1); + h = FastMath.max(h, 1.0e-12 * FastMath.abs(t0)); // avoids cancellation when computing t1 - t0 + if (h < getMinStep()) { + h = getMinStep(); + } + if (h > getMaxStep()) { + h = getMaxStep(); + } + if (! forward) { + h = -h; + } + + return h; + + } + + /** Filter the integration step. + * @param h signed step + * @param forward forward integration indicator + * @param acceptSmall if true, steps smaller than the minimal value + * are silently increased up to this value, if false such small + * steps generate an exception + * @return a bounded integration step (h if no bound is reach, or a bounded value) + * @exception NumberIsTooSmallException if the step is too small and acceptSmall is false + */ + protected double filterStep(final double h, final boolean forward, final boolean acceptSmall) + throws NumberIsTooSmallException { + + double filteredH = h; + if (FastMath.abs(h) < minStep) { + if (acceptSmall) { + filteredH = forward ? minStep : -minStep; + } else { + throw new NumberIsTooSmallException(LocalizedFormats.MINIMAL_STEPSIZE_REACHED_DURING_INTEGRATION, + FastMath.abs(h), minStep, true); + } + } + + if (filteredH > maxStep) { + filteredH = maxStep; + } else if (filteredH < -maxStep) { + filteredH = -maxStep; + } + + return filteredH; + + } + + /** {@inheritDoc} */ + @Override + public abstract void integrate (ExpandableStatefulODE equations, double t) + throws NumberIsTooSmallException, DimensionMismatchException, + MaxCountExceededException, NoBracketingException; + + /** {@inheritDoc} */ + @Override + public double getCurrentStepStart() { + return stepStart; + } + + /** Reset internal state to dummy values. */ + protected void resetInternalState() { + stepStart = Double.NaN; + stepSize = FastMath.sqrt(minStep * maxStep); + } + + /** Get the minimal step. + * @return minimal step + */ + public double getMinStep() { + return minStep; + } + + /** Get the maximal step. + * @return maximal step + */ + public double getMaxStep() { + return maxStep; + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaFieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaFieldIntegrator.java new file mode 100644 index 0000000..81e6d69 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaFieldIntegrator.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.Field; +import org.apache.commons.math3.RealFieldElement; +import org.apache.commons.math3.ode.FieldEquationsMapper; +import org.apache.commons.math3.ode.FieldODEStateAndDerivative; +import org.apache.commons.math3.util.MathArrays; + +/** + * This class implements the classical fourth order Runge-Kutta + * integrator for Ordinary Differential Equations (it is the most + * often used Runge-Kutta method). + * + * <p>This method is an explicit Runge-Kutta method, its Butcher-array + * is the following one : + * <pre> + * 0 | 0 0 0 0 + * 1/2 | 1/2 0 0 0 + * 1/2 | 0 1/2 0 0 + * 1 | 0 0 1 0 + * |-------------------- + * | 1/6 1/3 1/3 1/6 + * </pre> + * </p> + * + * @see EulerFieldIntegrator + * @see GillFieldIntegrator + * @see MidpointFieldIntegrator + * @see ThreeEighthesFieldIntegrator + * @see LutherFieldIntegrator + * @param <T> the type of the field elements + * @since 3.6 + */ + +public class ClassicalRungeKuttaFieldIntegrator<T extends RealFieldElement<T>> + extends RungeKuttaFieldIntegrator<T> { + + /** Simple constructor. + * Build a fourth-order Runge-Kutta integrator with the given step. + * @param field field to which the time and state vector elements belong + * @param step integration step + */ + public ClassicalRungeKuttaFieldIntegrator(final Field<T> field, final T step) { + super(field, "classical Runge-Kutta", step); + } + + /** {@inheritDoc} */ + public T[] getC() { + final T[] c = MathArrays.buildArray(getField(), 3); + c[0] = getField().getOne().multiply(0.5); + c[1] = c[0]; + c[2] = getField().getOne(); + return c; + } + + /** {@inheritDoc} */ + public T[][] getA() { + final T[][] a = MathArrays.buildArray(getField(), 3, -1); + for (int i = 0; i < a.length; ++i) { + a[i] = MathArrays.buildArray(getField(), i + 1); + } + a[0][0] = fraction(1, 2); + a[1][0] = getField().getZero(); + a[1][1] = a[0][0]; + a[2][0] = getField().getZero(); + a[2][1] = getField().getZero(); + a[2][2] = getField().getOne(); + return a; + } + + /** {@inheritDoc} */ + public T[] getB() { + final T[] b = MathArrays.buildArray(getField(), 4); + b[0] = fraction(1, 6); + b[1] = fraction(1, 3); + b[2] = b[1]; + b[3] = b[0]; + return b; + } + + /** {@inheritDoc} */ + @Override + protected ClassicalRungeKuttaFieldStepInterpolator<T> + createInterpolator(final boolean forward, T[][] yDotK, + final FieldODEStateAndDerivative<T> globalPreviousState, + final FieldODEStateAndDerivative<T> globalCurrentState, + final FieldEquationsMapper<T> mapper) { + return new ClassicalRungeKuttaFieldStepInterpolator<T>(getField(), forward, yDotK, + globalPreviousState, globalCurrentState, + globalPreviousState, globalCurrentState, + mapper); + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaFieldStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaFieldStepInterpolator.java new file mode 100644 index 0000000..4ad8f4e --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaFieldStepInterpolator.java @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.Field; +import org.apache.commons.math3.RealFieldElement; +import org.apache.commons.math3.ode.FieldEquationsMapper; +import org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** + * This class implements a step interpolator for the classical fourth + * order Runge-Kutta integrator. + * + * <p>This interpolator allows to compute dense output inside the last + * step computed. The interpolation equation is consistent with the + * integration scheme : + * <ul> + * <li>Using reference point at step start:<br> + * y(t<sub>n</sub> + θ h) = y (t<sub>n</sub>) + * + θ (h/6) [ (6 - 9 θ + 4 θ<sup>2</sup>) y'<sub>1</sub> + * + ( 6 θ - 4 θ<sup>2</sup>) (y'<sub>2</sub> + y'<sub>3</sub>) + * + ( -3 θ + 4 θ<sup>2</sup>) y'<sub>4</sub> + * ] + * </li> + * <li>Using reference point at step end:<br> + * y(t<sub>n</sub> + θ h) = y (t<sub>n</sub> + h) + * + (1 - θ) (h/6) [ (-4 θ^2 + 5 θ - 1) y'<sub>1</sub> + * +(4 θ^2 - 2 θ - 2) (y'<sub>2</sub> + y'<sub>3</sub>) + * -(4 θ^2 + θ + 1) y'<sub>4</sub> + * ] + * </li> + * </ul> + * </p> + * + * where θ belongs to [0 ; 1] and where y'<sub>1</sub> to y'<sub>4</sub> are the four + * evaluations of the derivatives already computed during the + * step.</p> + * + * @see ClassicalRungeKuttaFieldIntegrator + * @param <T> the type of the field elements + * @since 3.6 + */ + +class ClassicalRungeKuttaFieldStepInterpolator<T extends RealFieldElement<T>> + extends RungeKuttaFieldStepInterpolator<T> { + + /** Simple constructor. + * @param field field to which the time and state vector elements belong + * @param forward integration direction indicator + * @param yDotK slopes at the intermediate points + * @param globalPreviousState start of the global step + * @param globalCurrentState end of the global step + * @param softPreviousState start of the restricted step + * @param softCurrentState end of the restricted step + * @param mapper equations mapper for the all equations + */ + ClassicalRungeKuttaFieldStepInterpolator(final Field<T> field, final boolean forward, + final T[][] yDotK, + final FieldODEStateAndDerivative<T> globalPreviousState, + final FieldODEStateAndDerivative<T> globalCurrentState, + final FieldODEStateAndDerivative<T> softPreviousState, + final FieldODEStateAndDerivative<T> softCurrentState, + final FieldEquationsMapper<T> mapper) { + super(field, forward, yDotK, + globalPreviousState, globalCurrentState, softPreviousState, softCurrentState, + mapper); + } + + /** {@inheritDoc} */ + @Override + protected ClassicalRungeKuttaFieldStepInterpolator<T> create(final Field<T> newField, final boolean newForward, final T[][] newYDotK, + final FieldODEStateAndDerivative<T> newGlobalPreviousState, + final FieldODEStateAndDerivative<T> newGlobalCurrentState, + final FieldODEStateAndDerivative<T> newSoftPreviousState, + final FieldODEStateAndDerivative<T> newSoftCurrentState, + final FieldEquationsMapper<T> newMapper) { + return new ClassicalRungeKuttaFieldStepInterpolator<T>(newField, newForward, newYDotK, + newGlobalPreviousState, newGlobalCurrentState, + newSoftPreviousState, newSoftCurrentState, + newMapper); + } + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override + protected FieldODEStateAndDerivative<T> computeInterpolatedStateAndDerivatives(final FieldEquationsMapper<T> mapper, + final T time, final T theta, + final T thetaH, final T oneMinusThetaH) { + + final T one = time.getField().getOne(); + final T oneMinusTheta = one.subtract(theta); + final T oneMinus2Theta = one.subtract(theta.multiply(2)); + final T coeffDot1 = oneMinusTheta.multiply(oneMinus2Theta); + final T coeffDot23 = theta.multiply(oneMinusTheta).multiply(2); + final T coeffDot4 = theta.multiply(oneMinus2Theta).negate(); + final T[] interpolatedState; + final T[] interpolatedDerivatives; + + if (getGlobalPreviousState() != null && theta.getReal() <= 0.5) { + final T fourTheta2 = theta.multiply(theta).multiply(4); + final T s = thetaH.divide(6.0); + final T coeff1 = s.multiply(fourTheta2.subtract(theta.multiply(9)).add(6)); + final T coeff23 = s.multiply(theta.multiply(6).subtract(fourTheta2)); + final T coeff4 = s.multiply(fourTheta2.subtract(theta.multiply(3))); + interpolatedState = previousStateLinearCombination(coeff1, coeff23, coeff23, coeff4); + interpolatedDerivatives = derivativeLinearCombination(coeffDot1, coeffDot23, coeffDot23, coeffDot4); + } else { + final T fourTheta = theta.multiply(4); + final T s = oneMinusThetaH.divide(6); + final T coeff1 = s.multiply(theta.multiply(fourTheta.negate().add(5)).subtract(1)); + final T coeff23 = s.multiply(theta.multiply(fourTheta.subtract(2)).subtract(2)); + final T coeff4 = s.multiply(theta.multiply(fourTheta.negate().subtract(1)).subtract(1)); + interpolatedState = currentStateLinearCombination(coeff1, coeff23, coeff23, coeff4); + interpolatedDerivatives = derivativeLinearCombination(coeffDot1, coeffDot23, coeffDot23, coeffDot4); + } + + return new FieldODEStateAndDerivative<T>(time, interpolatedState, interpolatedDerivatives); + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaIntegrator.java new file mode 100644 index 0000000..ca915f1 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaIntegrator.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + + +/** + * This class implements the classical fourth order Runge-Kutta + * integrator for Ordinary Differential Equations (it is the most + * often used Runge-Kutta method). + * + * <p>This method is an explicit Runge-Kutta method, its Butcher-array + * is the following one : + * <pre> + * 0 | 0 0 0 0 + * 1/2 | 1/2 0 0 0 + * 1/2 | 0 1/2 0 0 + * 1 | 0 0 1 0 + * |-------------------- + * | 1/6 1/3 1/3 1/6 + * </pre> + * </p> + * + * @see EulerIntegrator + * @see GillIntegrator + * @see MidpointIntegrator + * @see ThreeEighthesIntegrator + * @see LutherIntegrator + * @since 1.2 + */ + +public class ClassicalRungeKuttaIntegrator extends RungeKuttaIntegrator { + + /** Time steps Butcher array. */ + private static final double[] STATIC_C = { + 1.0 / 2.0, 1.0 / 2.0, 1.0 + }; + + /** Internal weights Butcher array. */ + private static final double[][] STATIC_A = { + { 1.0 / 2.0 }, + { 0.0, 1.0 / 2.0 }, + { 0.0, 0.0, 1.0 } + }; + + /** Propagation weights Butcher array. */ + private static final double[] STATIC_B = { + 1.0 / 6.0, 1.0 / 3.0, 1.0 / 3.0, 1.0 / 6.0 + }; + + /** Simple constructor. + * Build a fourth-order Runge-Kutta integrator with the given + * step. + * @param step integration step + */ + public ClassicalRungeKuttaIntegrator(final double step) { + super("classical Runge-Kutta", STATIC_C, STATIC_A, STATIC_B, + new ClassicalRungeKuttaStepInterpolator(), step); + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaStepInterpolator.java new file mode 100644 index 0000000..296f5b5 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaStepInterpolator.java @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.ode.sampling.StepInterpolator; + +/** + * This class implements a step interpolator for the classical fourth + * order Runge-Kutta integrator. + * + * <p>This interpolator allows to compute dense output inside the last + * step computed. The interpolation equation is consistent with the + * integration scheme : + * <ul> + * <li>Using reference point at step start:<br> + * y(t<sub>n</sub> + θ h) = y (t<sub>n</sub>) + * + θ (h/6) [ (6 - 9 θ + 4 θ<sup>2</sup>) y'<sub>1</sub> + * + ( 6 θ - 4 θ<sup>2</sup>) (y'<sub>2</sub> + y'<sub>3</sub>) + * + ( -3 θ + 4 θ<sup>2</sup>) y'<sub>4</sub> + * ] + * </li> + * <li>Using reference point at step end:<br> + * y(t<sub>n</sub> + θ h) = y (t<sub>n</sub> + h) + * + (1 - θ) (h/6) [ (-4 θ^2 + 5 θ - 1) y'<sub>1</sub> + * +(4 θ^2 - 2 θ - 2) (y'<sub>2</sub> + y'<sub>3</sub>) + * -(4 θ^2 + θ + 1) y'<sub>4</sub> + * ] + * </li> + * </ul> + * </p> + * + * where θ belongs to [0 ; 1] and where y'<sub>1</sub> to y'<sub>4</sub> are the four + * evaluations of the derivatives already computed during the + * step.</p> + * + * @see ClassicalRungeKuttaIntegrator + * @since 1.2 + */ + +class ClassicalRungeKuttaStepInterpolator + extends RungeKuttaStepInterpolator { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 20111120L; + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link RungeKuttaStepInterpolator#reinitialize} method should be + * called before using the instance in order to initialize the + * internal arrays. This constructor is used only in order to delay + * the initialization in some cases. The {@link RungeKuttaIntegrator} + * class uses the prototyping design pattern to create the step + * interpolators by cloning an uninitialized model and latter initializing + * the copy. + */ + // CHECKSTYLE: stop RedundantModifier + // the public modifier here is needed for serialization + public ClassicalRungeKuttaStepInterpolator() { + } + // CHECKSTYLE: resume RedundantModifier + + /** Copy constructor. + * @param interpolator interpolator to copy from. The copy is a deep + * copy: its arrays are separated from the original arrays of the + * instance + */ + ClassicalRungeKuttaStepInterpolator(final ClassicalRungeKuttaStepInterpolator interpolator) { + super(interpolator); + } + + /** {@inheritDoc} */ + @Override + protected StepInterpolator doCopy() { + return new ClassicalRungeKuttaStepInterpolator(this); + } + + /** {@inheritDoc} */ + @Override + protected void computeInterpolatedStateAndDerivatives(final double theta, + final double oneMinusThetaH) { + + final double oneMinusTheta = 1 - theta; + final double oneMinus2Theta = 1 - 2 * theta; + final double coeffDot1 = oneMinusTheta * oneMinus2Theta; + final double coeffDot23 = 2 * theta * oneMinusTheta; + final double coeffDot4 = -theta * oneMinus2Theta; + if ((previousState != null) && (theta <= 0.5)) { + final double fourTheta2 = 4 * theta * theta; + final double s = theta * h / 6.0; + final double coeff1 = s * ( 6 - 9 * theta + fourTheta2); + final double coeff23 = s * ( 6 * theta - fourTheta2); + final double coeff4 = s * (-3 * theta + fourTheta2); + for (int i = 0; i < interpolatedState.length; ++i) { + final double yDot1 = yDotK[0][i]; + final double yDot23 = yDotK[1][i] + yDotK[2][i]; + final double yDot4 = yDotK[3][i]; + interpolatedState[i] = + previousState[i] + coeff1 * yDot1 + coeff23 * yDot23 + coeff4 * yDot4; + interpolatedDerivatives[i] = + coeffDot1 * yDot1 + coeffDot23 * yDot23 + coeffDot4 * yDot4; + } + } else { + final double fourTheta = 4 * theta; + final double s = oneMinusThetaH / 6.0; + final double coeff1 = s * ((-fourTheta + 5) * theta - 1); + final double coeff23 = s * (( fourTheta - 2) * theta - 2); + final double coeff4 = s * ((-fourTheta - 1) * theta - 1); + for (int i = 0; i < interpolatedState.length; ++i) { + final double yDot1 = yDotK[0][i]; + final double yDot23 = yDotK[1][i] + yDotK[2][i]; + final double yDot4 = yDotK[3][i]; + interpolatedState[i] = + currentState[i] + coeff1 * yDot1 + coeff23 * yDot23 + coeff4 * yDot4; + interpolatedDerivatives[i] = + coeffDot1 * yDot1 + coeffDot23 * yDot23 + coeffDot4 * yDot4; + } + } + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince54FieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince54FieldIntegrator.java new file mode 100644 index 0000000..5cdd828 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince54FieldIntegrator.java @@ -0,0 +1,232 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.Field; +import org.apache.commons.math3.RealFieldElement; +import org.apache.commons.math3.ode.FieldEquationsMapper; +import org.apache.commons.math3.ode.FieldODEStateAndDerivative; +import org.apache.commons.math3.util.MathArrays; +import org.apache.commons.math3.util.MathUtils; + + +/** + * This class implements the 5(4) Dormand-Prince integrator for Ordinary + * Differential Equations. + + * <p>This integrator is an embedded Runge-Kutta integrator + * of order 5(4) used in local extrapolation mode (i.e. the solution + * is computed using the high order formula) with stepsize control + * (and automatic step initialization) and continuous output. This + * method uses 7 functions evaluations per step. However, since this + * is an <i>fsal</i>, the last evaluation of one step is the same as + * the first evaluation of the next step and hence can be avoided. So + * the cost is really 6 functions evaluations per step.</p> + * + * <p>This method has been published (whithout the continuous output + * that was added by Shampine in 1986) in the following article : + * <pre> + * A family of embedded Runge-Kutta formulae + * J. R. Dormand and P. J. Prince + * Journal of Computational and Applied Mathematics + * volume 6, no 1, 1980, pp. 19-26 + * </pre></p> + * + * @param <T> the type of the field elements + * @since 3.6 + */ + +public class DormandPrince54FieldIntegrator<T extends RealFieldElement<T>> + extends EmbeddedRungeKuttaFieldIntegrator<T> { + + /** Integrator method name. */ + private static final String METHOD_NAME = "Dormand-Prince 5(4)"; + + /** Error array, element 1. */ + private final T e1; + + // element 2 is zero, so it is neither stored nor used + + /** Error array, element 3. */ + private final T e3; + + /** Error array, element 4. */ + private final T e4; + + /** Error array, element 5. */ + private final T e5; + + /** Error array, element 6. */ + private final T e6; + + /** Error array, element 7. */ + private final T e7; + + /** Simple constructor. + * Build a fifth order Dormand-Prince integrator with the given step bounds + * @param field field to which the time and state vector elements belong + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + */ + public DormandPrince54FieldIntegrator(final Field<T> field, + final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) { + super(field, METHOD_NAME, 6, + minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance); + e1 = fraction( 71, 57600); + e3 = fraction( -71, 16695); + e4 = fraction( 71, 1920); + e5 = fraction(-17253, 339200); + e6 = fraction( 22, 525); + e7 = fraction( -1, 40); + } + + /** Simple constructor. + * Build a fifth order Dormand-Prince integrator with the given step bounds + * @param field field to which the time and state vector elements belong + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + */ + public DormandPrince54FieldIntegrator(final Field<T> field, + final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) { + super(field, METHOD_NAME, 6, + minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance); + e1 = fraction( 71, 57600); + e3 = fraction( -71, 16695); + e4 = fraction( 71, 1920); + e5 = fraction(-17253, 339200); + e6 = fraction( 22, 525); + e7 = fraction( -1, 40); + } + + /** {@inheritDoc} */ + public T[] getC() { + final T[] c = MathArrays.buildArray(getField(), 6); + c[0] = fraction(1, 5); + c[1] = fraction(3, 10); + c[2] = fraction(4, 5); + c[3] = fraction(8, 9); + c[4] = getField().getOne(); + c[5] = getField().getOne(); + return c; + } + + /** {@inheritDoc} */ + public T[][] getA() { + final T[][] a = MathArrays.buildArray(getField(), 6, -1); + for (int i = 0; i < a.length; ++i) { + a[i] = MathArrays.buildArray(getField(), i + 1); + } + a[0][0] = fraction( 1, 5); + a[1][0] = fraction( 3, 40); + a[1][1] = fraction( 9, 40); + a[2][0] = fraction( 44, 45); + a[2][1] = fraction( -56, 15); + a[2][2] = fraction( 32, 9); + a[3][0] = fraction( 19372, 6561); + a[3][1] = fraction(-25360, 2187); + a[3][2] = fraction( 64448, 6561); + a[3][3] = fraction( -212, 729); + a[4][0] = fraction( 9017, 3168); + a[4][1] = fraction( -355, 33); + a[4][2] = fraction( 46732, 5247); + a[4][3] = fraction( 49, 176); + a[4][4] = fraction( -5103, 18656); + a[5][0] = fraction( 35, 384); + a[5][1] = getField().getZero(); + a[5][2] = fraction( 500, 1113); + a[5][3] = fraction( 125, 192); + a[5][4] = fraction( -2187, 6784); + a[5][5] = fraction( 11, 84); + return a; + } + + /** {@inheritDoc} */ + public T[] getB() { + final T[] b = MathArrays.buildArray(getField(), 7); + b[0] = fraction( 35, 384); + b[1] = getField().getZero(); + b[2] = fraction( 500, 1113); + b[3] = fraction( 125, 192); + b[4] = fraction(-2187, 6784); + b[5] = fraction( 11, 84); + b[6] = getField().getZero(); + return b; + } + + /** {@inheritDoc} */ + @Override + protected DormandPrince54FieldStepInterpolator<T> + createInterpolator(final boolean forward, T[][] yDotK, + final FieldODEStateAndDerivative<T> globalPreviousState, + final FieldODEStateAndDerivative<T> globalCurrentState, final FieldEquationsMapper<T> mapper) { + return new DormandPrince54FieldStepInterpolator<T>(getField(), forward, yDotK, + globalPreviousState, globalCurrentState, + globalPreviousState, globalCurrentState, + mapper); + } + + /** {@inheritDoc} */ + @Override + public int getOrder() { + return 5; + } + + /** {@inheritDoc} */ + @Override + protected T estimateError(final T[][] yDotK, final T[] y0, final T[] y1, final T h) { + + T error = getField().getZero(); + + for (int j = 0; j < mainSetDimension; ++j) { + final T errSum = yDotK[0][j].multiply(e1). + add(yDotK[2][j].multiply(e3)). + add(yDotK[3][j].multiply(e4)). + add(yDotK[4][j].multiply(e5)). + add(yDotK[5][j].multiply(e6)). + add(yDotK[6][j].multiply(e7)); + + final T yScale = MathUtils.max(y0[j].abs(), y1[j].abs()); + final T tol = (vecAbsoluteTolerance == null) ? + yScale.multiply(scalRelativeTolerance).add(scalAbsoluteTolerance) : + yScale.multiply(vecRelativeTolerance[j]).add(vecAbsoluteTolerance[j]); + final T ratio = h.multiply(errSum).divide(tol); + error = error.add(ratio.multiply(ratio)); + + } + + return error.divide(mainSetDimension).sqrt(); + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince54FieldStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince54FieldStepInterpolator.java new file mode 100644 index 0000000..62a6fa3 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince54FieldStepInterpolator.java @@ -0,0 +1,249 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.Field; +import org.apache.commons.math3.RealFieldElement; +import org.apache.commons.math3.ode.FieldEquationsMapper; +import org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** + * This class represents an interpolator over the last step during an + * ODE integration for the 5(4) Dormand-Prince integrator. + * + * @see DormandPrince54Integrator + * + * @param <T> the type of the field elements + * @since 3.6 + */ + +class DormandPrince54FieldStepInterpolator<T extends RealFieldElement<T>> + extends RungeKuttaFieldStepInterpolator<T> { + + /** Last row of the Butcher-array internal weights, element 0. */ + private final T a70; + + // element 1 is zero, so it is neither stored nor used + + /** Last row of the Butcher-array internal weights, element 2. */ + private final T a72; + + /** Last row of the Butcher-array internal weights, element 3. */ + private final T a73; + + /** Last row of the Butcher-array internal weights, element 4. */ + private final T a74; + + /** Last row of the Butcher-array internal weights, element 5. */ + private final T a75; + + /** Shampine (1986) Dense output, element 0. */ + private final T d0; + + // element 1 is zero, so it is neither stored nor used + + /** Shampine (1986) Dense output, element 2. */ + private final T d2; + + /** Shampine (1986) Dense output, element 3. */ + private final T d3; + + /** Shampine (1986) Dense output, element 4. */ + private final T d4; + + /** Shampine (1986) Dense output, element 5. */ + private final T d5; + + /** Shampine (1986) Dense output, element 6. */ + private final T d6; + + /** Simple constructor. + * @param field field to which the time and state vector elements belong + * @param forward integration direction indicator + * @param yDotK slopes at the intermediate points + * @param globalPreviousState start of the global step + * @param globalCurrentState end of the global step + * @param softPreviousState start of the restricted step + * @param softCurrentState end of the restricted step + * @param mapper equations mapper for the all equations + */ + DormandPrince54FieldStepInterpolator(final Field<T> field, final boolean forward, + final T[][] yDotK, + final FieldODEStateAndDerivative<T> globalPreviousState, + final FieldODEStateAndDerivative<T> globalCurrentState, + final FieldODEStateAndDerivative<T> softPreviousState, + final FieldODEStateAndDerivative<T> softCurrentState, + final FieldEquationsMapper<T> mapper) { + super(field, forward, yDotK, + globalPreviousState, globalCurrentState, softPreviousState, softCurrentState, + mapper); + final T one = field.getOne(); + a70 = one.multiply( 35.0).divide( 384.0); + a72 = one.multiply( 500.0).divide(1113.0); + a73 = one.multiply( 125.0).divide( 192.0); + a74 = one.multiply(-2187.0).divide(6784.0); + a75 = one.multiply( 11.0).divide( 84.0); + d0 = one.multiply(-12715105075.0).divide( 11282082432.0); + d2 = one.multiply( 87487479700.0).divide( 32700410799.0); + d3 = one.multiply(-10690763975.0).divide( 1880347072.0); + d4 = one.multiply(701980252875.0).divide(199316789632.0); + d5 = one.multiply( -1453857185.0).divide( 822651844.0); + d6 = one.multiply( 69997945.0).divide( 29380423.0); + } + + /** {@inheritDoc} */ + @Override + protected DormandPrince54FieldStepInterpolator<T> create(final Field<T> newField, final boolean newForward, final T[][] newYDotK, + final FieldODEStateAndDerivative<T> newGlobalPreviousState, + final FieldODEStateAndDerivative<T> newGlobalCurrentState, + final FieldODEStateAndDerivative<T> newSoftPreviousState, + final FieldODEStateAndDerivative<T> newSoftCurrentState, + final FieldEquationsMapper<T> newMapper) { + return new DormandPrince54FieldStepInterpolator<T>(newField, newForward, newYDotK, + newGlobalPreviousState, newGlobalCurrentState, + newSoftPreviousState, newSoftCurrentState, + newMapper); + } + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override + protected FieldODEStateAndDerivative<T> computeInterpolatedStateAndDerivatives(final FieldEquationsMapper<T> mapper, + final T time, final T theta, + final T thetaH, final T oneMinusThetaH) { + + // interpolate + final T one = time.getField().getOne(); + final T eta = one.subtract(theta); + final T twoTheta = theta.multiply(2); + final T dot2 = one.subtract(twoTheta); + final T dot3 = theta.multiply(theta.multiply(-3).add(2)); + final T dot4 = twoTheta.multiply(theta.multiply(twoTheta.subtract(3)).add(1)); + final T[] interpolatedState; + final T[] interpolatedDerivatives; + if (getGlobalPreviousState() != null && theta.getReal() <= 0.5) { + final T f1 = thetaH; + final T f2 = f1.multiply(eta); + final T f3 = f2.multiply(theta); + final T f4 = f3.multiply(eta); + final T coeff0 = f1.multiply(a70). + subtract(f2.multiply(a70.subtract(1))). + add(f3.multiply(a70.multiply(2).subtract(1))). + add(f4.multiply(d0)); + final T coeff1 = time.getField().getZero(); + final T coeff2 = f1.multiply(a72). + subtract(f2.multiply(a72)). + add(f3.multiply(a72.multiply(2))). + add(f4.multiply(d2)); + final T coeff3 = f1.multiply(a73). + subtract(f2.multiply(a73)). + add(f3.multiply(a73.multiply(2))). + add(f4.multiply(d3)); + final T coeff4 = f1.multiply(a74). + subtract(f2.multiply(a74)). + add(f3.multiply(a74.multiply(2))). + add(f4.multiply(d4)); + final T coeff5 = f1.multiply(a75). + subtract(f2.multiply(a75)). + add(f3.multiply(a75.multiply(2))). + add(f4.multiply(d5)); + final T coeff6 = f4.multiply(d6).subtract(f3); + final T coeffDot0 = a70. + subtract(dot2.multiply(a70.subtract(1))). + add(dot3.multiply(a70.multiply(2).subtract(1))). + add(dot4.multiply(d0)); + final T coeffDot1 = time.getField().getZero(); + final T coeffDot2 = a72. + subtract(dot2.multiply(a72)). + add(dot3.multiply(a72.multiply(2))). + add(dot4.multiply(d2)); + final T coeffDot3 = a73. + subtract(dot2.multiply(a73)). + add(dot3.multiply(a73.multiply(2))). + add(dot4.multiply(d3)); + final T coeffDot4 = a74. + subtract(dot2.multiply(a74)). + add(dot3.multiply(a74.multiply(2))). + add(dot4.multiply(d4)); + final T coeffDot5 = a75. + subtract(dot2.multiply(a75)). + add(dot3.multiply(a75.multiply(2))). + add(dot4.multiply(d5)); + final T coeffDot6 = dot4.multiply(d6).subtract(dot3); + interpolatedState = previousStateLinearCombination(coeff0, coeff1, coeff2, coeff3, + coeff4, coeff5, coeff6); + interpolatedDerivatives = derivativeLinearCombination(coeffDot0, coeffDot1, coeffDot2, coeffDot3, + coeffDot4, coeffDot5, coeffDot6); + } else { + final T f1 = oneMinusThetaH.negate(); + final T f2 = oneMinusThetaH.multiply(theta); + final T f3 = f2.multiply(theta); + final T f4 = f3.multiply(eta); + final T coeff0 = f1.multiply(a70). + subtract(f2.multiply(a70.subtract(1))). + add(f3.multiply(a70.multiply(2).subtract(1))). + add(f4.multiply(d0)); + final T coeff1 = time.getField().getZero(); + final T coeff2 = f1.multiply(a72). + subtract(f2.multiply(a72)). + add(f3.multiply(a72.multiply(2))). + add(f4.multiply(d2)); + final T coeff3 = f1.multiply(a73). + subtract(f2.multiply(a73)). + add(f3.multiply(a73.multiply(2))). + add(f4.multiply(d3)); + final T coeff4 = f1.multiply(a74). + subtract(f2.multiply(a74)). + add(f3.multiply(a74.multiply(2))). + add(f4.multiply(d4)); + final T coeff5 = f1.multiply(a75). + subtract(f2.multiply(a75)). + add(f3.multiply(a75.multiply(2))). + add(f4.multiply(d5)); + final T coeff6 = f4.multiply(d6).subtract(f3); + final T coeffDot0 = a70. + subtract(dot2.multiply(a70.subtract(1))). + add(dot3.multiply(a70.multiply(2).subtract(1))). + add(dot4.multiply(d0)); + final T coeffDot1 = time.getField().getZero(); + final T coeffDot2 = a72. + subtract(dot2.multiply(a72)). + add(dot3.multiply(a72.multiply(2))). + add(dot4.multiply(d2)); + final T coeffDot3 = a73. + subtract(dot2.multiply(a73)). + add(dot3.multiply(a73.multiply(2))). + add(dot4.multiply(d3)); + final T coeffDot4 = a74. + subtract(dot2.multiply(a74)). + add(dot3.multiply(a74.multiply(2))). + add(dot4.multiply(d4)); + final T coeffDot5 = a75. + subtract(dot2.multiply(a75)). + add(dot3.multiply(a75.multiply(2))). + add(dot4.multiply(d5)); + final T coeffDot6 = dot4.multiply(d6).subtract(dot3); + interpolatedState = currentStateLinearCombination(coeff0, coeff1, coeff2, coeff3, + coeff4, coeff5, coeff6); + interpolatedDerivatives = derivativeLinearCombination(coeffDot0, coeffDot1, coeffDot2, coeffDot3, + coeffDot4, coeffDot5, coeffDot6); + } + return new FieldODEStateAndDerivative<T>(time, interpolatedState, interpolatedDerivatives); + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince54Integrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince54Integrator.java new file mode 100644 index 0000000..45229b2 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince54Integrator.java @@ -0,0 +1,161 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.util.FastMath; + + +/** + * This class implements the 5(4) Dormand-Prince integrator for Ordinary + * Differential Equations. + + * <p>This integrator is an embedded Runge-Kutta integrator + * of order 5(4) used in local extrapolation mode (i.e. the solution + * is computed using the high order formula) with stepsize control + * (and automatic step initialization) and continuous output. This + * method uses 7 functions evaluations per step. However, since this + * is an <i>fsal</i>, the last evaluation of one step is the same as + * the first evaluation of the next step and hence can be avoided. So + * the cost is really 6 functions evaluations per step.</p> + * + * <p>This method has been published (whithout the continuous output + * that was added by Shampine in 1986) in the following article : + * <pre> + * A family of embedded Runge-Kutta formulae + * J. R. Dormand and P. J. Prince + * Journal of Computational and Applied Mathematics + * volume 6, no 1, 1980, pp. 19-26 + * </pre></p> + * + * @since 1.2 + */ + +public class DormandPrince54Integrator extends EmbeddedRungeKuttaIntegrator { + + /** Integrator method name. */ + private static final String METHOD_NAME = "Dormand-Prince 5(4)"; + + /** Time steps Butcher array. */ + private static final double[] STATIC_C = { + 1.0/5.0, 3.0/10.0, 4.0/5.0, 8.0/9.0, 1.0, 1.0 + }; + + /** Internal weights Butcher array. */ + private static final double[][] STATIC_A = { + {1.0/5.0}, + {3.0/40.0, 9.0/40.0}, + {44.0/45.0, -56.0/15.0, 32.0/9.0}, + {19372.0/6561.0, -25360.0/2187.0, 64448.0/6561.0, -212.0/729.0}, + {9017.0/3168.0, -355.0/33.0, 46732.0/5247.0, 49.0/176.0, -5103.0/18656.0}, + {35.0/384.0, 0.0, 500.0/1113.0, 125.0/192.0, -2187.0/6784.0, 11.0/84.0} + }; + + /** Propagation weights Butcher array. */ + private static final double[] STATIC_B = { + 35.0/384.0, 0.0, 500.0/1113.0, 125.0/192.0, -2187.0/6784.0, 11.0/84.0, 0.0 + }; + + /** Error array, element 1. */ + private static final double E1 = 71.0 / 57600.0; + + // element 2 is zero, so it is neither stored nor used + + /** Error array, element 3. */ + private static final double E3 = -71.0 / 16695.0; + + /** Error array, element 4. */ + private static final double E4 = 71.0 / 1920.0; + + /** Error array, element 5. */ + private static final double E5 = -17253.0 / 339200.0; + + /** Error array, element 6. */ + private static final double E6 = 22.0 / 525.0; + + /** Error array, element 7. */ + private static final double E7 = -1.0 / 40.0; + + /** Simple constructor. + * Build a fifth order Dormand-Prince integrator with the given step bounds + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + */ + public DormandPrince54Integrator(final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) { + super(METHOD_NAME, true, STATIC_C, STATIC_A, STATIC_B, new DormandPrince54StepInterpolator(), + minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance); + } + + /** Simple constructor. + * Build a fifth order Dormand-Prince integrator with the given step bounds + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + */ + public DormandPrince54Integrator(final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) { + super(METHOD_NAME, true, STATIC_C, STATIC_A, STATIC_B, new DormandPrince54StepInterpolator(), + minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance); + } + + /** {@inheritDoc} */ + @Override + public int getOrder() { + return 5; + } + + /** {@inheritDoc} */ + @Override + protected double estimateError(final double[][] yDotK, + final double[] y0, final double[] y1, + final double h) { + + double error = 0; + + for (int j = 0; j < mainSetDimension; ++j) { + final double errSum = E1 * yDotK[0][j] + E3 * yDotK[2][j] + + E4 * yDotK[3][j] + E5 * yDotK[4][j] + + E6 * yDotK[5][j] + E7 * yDotK[6][j]; + + final double yScale = FastMath.max(FastMath.abs(y0[j]), FastMath.abs(y1[j])); + final double tol = (vecAbsoluteTolerance == null) ? + (scalAbsoluteTolerance + scalRelativeTolerance * yScale) : + (vecAbsoluteTolerance[j] + vecRelativeTolerance[j] * yScale); + final double ratio = h * errSum / tol; + error += ratio * ratio; + + } + + return FastMath.sqrt(error / mainSetDimension); + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince54StepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince54StepInterpolator.java new file mode 100644 index 0000000..04aa970 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince54StepInterpolator.java @@ -0,0 +1,225 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.ode.AbstractIntegrator; +import org.apache.commons.math3.ode.EquationsMapper; +import org.apache.commons.math3.ode.sampling.StepInterpolator; + +/** + * This class represents an interpolator over the last step during an + * ODE integration for the 5(4) Dormand-Prince integrator. + * + * @see DormandPrince54Integrator + * + * @since 1.2 + */ + +class DormandPrince54StepInterpolator + extends RungeKuttaStepInterpolator { + + /** Last row of the Butcher-array internal weights, element 0. */ + private static final double A70 = 35.0 / 384.0; + + // element 1 is zero, so it is neither stored nor used + + /** Last row of the Butcher-array internal weights, element 2. */ + private static final double A72 = 500.0 / 1113.0; + + /** Last row of the Butcher-array internal weights, element 3. */ + private static final double A73 = 125.0 / 192.0; + + /** Last row of the Butcher-array internal weights, element 4. */ + private static final double A74 = -2187.0 / 6784.0; + + /** Last row of the Butcher-array internal weights, element 5. */ + private static final double A75 = 11.0 / 84.0; + + /** Shampine (1986) Dense output, element 0. */ + private static final double D0 = -12715105075.0 / 11282082432.0; + + // element 1 is zero, so it is neither stored nor used + + /** Shampine (1986) Dense output, element 2. */ + private static final double D2 = 87487479700.0 / 32700410799.0; + + /** Shampine (1986) Dense output, element 3. */ + private static final double D3 = -10690763975.0 / 1880347072.0; + + /** Shampine (1986) Dense output, element 4. */ + private static final double D4 = 701980252875.0 / 199316789632.0; + + /** Shampine (1986) Dense output, element 5. */ + private static final double D5 = -1453857185.0 / 822651844.0; + + /** Shampine (1986) Dense output, element 6. */ + private static final double D6 = 69997945.0 / 29380423.0; + + /** Serializable version identifier. */ + private static final long serialVersionUID = 20111120L; + + /** First vector for interpolation. */ + private double[] v1; + + /** Second vector for interpolation. */ + private double[] v2; + + /** Third vector for interpolation. */ + private double[] v3; + + /** Fourth vector for interpolation. */ + private double[] v4; + + /** Initialization indicator for the interpolation vectors. */ + private boolean vectorsInitialized; + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link #reinitialize} method should be called before using the + * instance in order to initialize the internal arrays. This + * constructor is used only in order to delay the initialization in + * some cases. The {@link EmbeddedRungeKuttaIntegrator} uses the + * prototyping design pattern to create the step interpolators by + * cloning an uninitialized model and latter initializing the copy. + */ + // CHECKSTYLE: stop RedundantModifier + // the public modifier here is needed for serialization + public DormandPrince54StepInterpolator() { + super(); + v1 = null; + v2 = null; + v3 = null; + v4 = null; + vectorsInitialized = false; + } + // CHECKSTYLE: resume RedundantModifier + + /** Copy constructor. + * @param interpolator interpolator to copy from. The copy is a deep + * copy: its arrays are separated from the original arrays of the + * instance + */ + DormandPrince54StepInterpolator(final DormandPrince54StepInterpolator interpolator) { + + super(interpolator); + + if (interpolator.v1 == null) { + + v1 = null; + v2 = null; + v3 = null; + v4 = null; + vectorsInitialized = false; + + } else { + + v1 = interpolator.v1.clone(); + v2 = interpolator.v2.clone(); + v3 = interpolator.v3.clone(); + v4 = interpolator.v4.clone(); + vectorsInitialized = interpolator.vectorsInitialized; + + } + + } + + /** {@inheritDoc} */ + @Override + protected StepInterpolator doCopy() { + return new DormandPrince54StepInterpolator(this); + } + + + /** {@inheritDoc} */ + @Override + public void reinitialize(final AbstractIntegrator integrator, + final double[] y, final double[][] yDotK, final boolean forward, + final EquationsMapper primaryMapper, + final EquationsMapper[] secondaryMappers) { + super.reinitialize(integrator, y, yDotK, forward, primaryMapper, secondaryMappers); + v1 = null; + v2 = null; + v3 = null; + v4 = null; + vectorsInitialized = false; + } + + /** {@inheritDoc} */ + @Override + public void storeTime(final double t) { + super.storeTime(t); + vectorsInitialized = false; + } + + /** {@inheritDoc} */ + @Override + protected void computeInterpolatedStateAndDerivatives(final double theta, + final double oneMinusThetaH) { + + if (! vectorsInitialized) { + + if (v1 == null) { + v1 = new double[interpolatedState.length]; + v2 = new double[interpolatedState.length]; + v3 = new double[interpolatedState.length]; + v4 = new double[interpolatedState.length]; + } + + // no step finalization is needed for this interpolator + + // we need to compute the interpolation vectors for this time step + for (int i = 0; i < interpolatedState.length; ++i) { + final double yDot0 = yDotK[0][i]; + final double yDot2 = yDotK[2][i]; + final double yDot3 = yDotK[3][i]; + final double yDot4 = yDotK[4][i]; + final double yDot5 = yDotK[5][i]; + final double yDot6 = yDotK[6][i]; + v1[i] = A70 * yDot0 + A72 * yDot2 + A73 * yDot3 + A74 * yDot4 + A75 * yDot5; + v2[i] = yDot0 - v1[i]; + v3[i] = v1[i] - v2[i] - yDot6; + v4[i] = D0 * yDot0 + D2 * yDot2 + D3 * yDot3 + D4 * yDot4 + D5 * yDot5 + D6 * yDot6; + } + + vectorsInitialized = true; + + } + + // interpolate + final double eta = 1 - theta; + final double twoTheta = 2 * theta; + final double dot2 = 1 - twoTheta; + final double dot3 = theta * (2 - 3 * theta); + final double dot4 = twoTheta * (1 + theta * (twoTheta - 3)); + if ((previousState != null) && (theta <= 0.5)) { + for (int i = 0; i < interpolatedState.length; ++i) { + interpolatedState[i] = + previousState[i] + theta * h * (v1[i] + eta * (v2[i] + theta * (v3[i] + eta * v4[i]))); + interpolatedDerivatives[i] = v1[i] + dot2 * v2[i] + dot3 * v3[i] + dot4 * v4[i]; + } + } else { + for (int i = 0; i < interpolatedState.length; ++i) { + interpolatedState[i] = + currentState[i] - oneMinusThetaH * (v1[i] - theta * (v2[i] + theta * (v3[i] + eta * v4[i]))); + interpolatedDerivatives[i] = v1[i] + dot2 * v2[i] + dot3 * v3[i] + dot4 * v4[i]; + } + } + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince853FieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince853FieldIntegrator.java new file mode 100644 index 0000000..3111756 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince853FieldIntegrator.java @@ -0,0 +1,454 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.Field; +import org.apache.commons.math3.RealFieldElement; +import org.apache.commons.math3.ode.FieldEquationsMapper; +import org.apache.commons.math3.ode.FieldODEStateAndDerivative; +import org.apache.commons.math3.util.MathArrays; +import org.apache.commons.math3.util.MathUtils; + + +/** + * This class implements the 8(5,3) Dormand-Prince integrator for Ordinary + * Differential Equations. + * + * <p>This integrator is an embedded Runge-Kutta integrator + * of order 8(5,3) used in local extrapolation mode (i.e. the solution + * is computed using the high order formula) with stepsize control + * (and automatic step initialization) and continuous output. This + * method uses 12 functions evaluations per step for integration and 4 + * evaluations for interpolation. However, since the first + * interpolation evaluation is the same as the first integration + * evaluation of the next step, we have included it in the integrator + * rather than in the interpolator and specified the method was an + * <i>fsal</i>. Hence, despite we have 13 stages here, the cost is + * really 12 evaluations per step even if no interpolation is done, + * and the overcost of interpolation is only 3 evaluations.</p> + * + * <p>This method is based on an 8(6) method by Dormand and Prince + * (i.e. order 8 for the integration and order 6 for error estimation) + * modified by Hairer and Wanner to use a 5th order error estimator + * with 3rd order correction. This modification was introduced because + * the original method failed in some cases (wrong steps can be + * accepted when step size is too large, for example in the + * Brusselator problem) and also had <i>severe difficulties when + * applied to problems with discontinuities</i>. This modification is + * explained in the second edition of the first volume (Nonstiff + * Problems) of the reference book by Hairer, Norsett and Wanner: + * <i>Solving Ordinary Differential Equations</i> (Springer-Verlag, + * ISBN 3-540-56670-8).</p> + * + * @param <T> the type of the field elements + * @since 3.6 + */ + +public class DormandPrince853FieldIntegrator<T extends RealFieldElement<T>> + extends EmbeddedRungeKuttaFieldIntegrator<T> { + + /** Integrator method name. */ + private static final String METHOD_NAME = "Dormand-Prince 8 (5, 3)"; + + /** First error weights array, element 1. */ + private final T e1_01; + + // elements 2 to 5 are zero, so they are neither stored nor used + + /** First error weights array, element 6. */ + private final T e1_06; + + /** First error weights array, element 7. */ + private final T e1_07; + + /** First error weights array, element 8. */ + private final T e1_08; + + /** First error weights array, element 9. */ + private final T e1_09; + + /** First error weights array, element 10. */ + private final T e1_10; + + /** First error weights array, element 11. */ + private final T e1_11; + + /** First error weights array, element 12. */ + private final T e1_12; + + + /** Second error weights array, element 1. */ + private final T e2_01; + + // elements 2 to 5 are zero, so they are neither stored nor used + + /** Second error weights array, element 6. */ + private final T e2_06; + + /** Second error weights array, element 7. */ + private final T e2_07; + + /** Second error weights array, element 8. */ + private final T e2_08; + + /** Second error weights array, element 9. */ + private final T e2_09; + + /** Second error weights array, element 10. */ + private final T e2_10; + + /** Second error weights array, element 11. */ + private final T e2_11; + + /** Second error weights array, element 12. */ + private final T e2_12; + + /** Simple constructor. + * Build an eighth order Dormand-Prince integrator with the given step bounds + * @param field field to which the time and state vector elements belong + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + */ + public DormandPrince853FieldIntegrator(final Field<T> field, + final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) { + super(field, METHOD_NAME, 12, + minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance); + e1_01 = fraction( 116092271.0, 8848465920.0); + e1_06 = fraction( -1871647.0, 1527680.0); + e1_07 = fraction( -69799717.0, 140793660.0); + e1_08 = fraction( 1230164450203.0, 739113984000.0); + e1_09 = fraction(-1980813971228885.0, 5654156025964544.0); + e1_10 = fraction( 464500805.0, 1389975552.0); + e1_11 = fraction( 1606764981773.0, 19613062656000.0); + e1_12 = fraction( -137909.0, 6168960.0); + e2_01 = fraction( -364463.0, 1920240.0); + e2_06 = fraction( 3399327.0, 763840.0); + e2_07 = fraction( 66578432.0, 35198415.0); + e2_08 = fraction( -1674902723.0, 288716400.0); + e2_09 = fraction( -74684743568175.0, 176692375811392.0); + e2_10 = fraction( -734375.0, 4826304.0); + e2_11 = fraction( 171414593.0, 851261400.0); + e2_12 = fraction( 69869.0, 3084480.0); + } + + /** Simple constructor. + * Build an eighth order Dormand-Prince integrator with the given step bounds + * @param field field to which the time and state vector elements belong + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + */ + public DormandPrince853FieldIntegrator(final Field<T> field, + final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) { + super(field, METHOD_NAME, 12, + minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance); + e1_01 = fraction( 116092271.0, 8848465920.0); + e1_06 = fraction( -1871647.0, 1527680.0); + e1_07 = fraction( -69799717.0, 140793660.0); + e1_08 = fraction( 1230164450203.0, 739113984000.0); + e1_09 = fraction(-1980813971228885.0, 5654156025964544.0); + e1_10 = fraction( 464500805.0, 1389975552.0); + e1_11 = fraction( 1606764981773.0, 19613062656000.0); + e1_12 = fraction( -137909.0, 6168960.0); + e2_01 = fraction( -364463.0, 1920240.0); + e2_06 = fraction( 3399327.0, 763840.0); + e2_07 = fraction( 66578432.0, 35198415.0); + e2_08 = fraction( -1674902723.0, 288716400.0); + e2_09 = fraction( -74684743568175.0, 176692375811392.0); + e2_10 = fraction( -734375.0, 4826304.0); + e2_11 = fraction( 171414593.0, 851261400.0); + e2_12 = fraction( 69869.0, 3084480.0); + } + + /** {@inheritDoc} */ + public T[] getC() { + + final T sqrt6 = getField().getOne().multiply(6).sqrt(); + + final T[] c = MathArrays.buildArray(getField(), 15); + c[ 0] = sqrt6.add(-6).divide(-67.5); + c[ 1] = sqrt6.add(-6).divide(-45.0); + c[ 2] = sqrt6.add(-6).divide(-30.0); + c[ 3] = sqrt6.add( 6).divide( 30.0); + c[ 4] = fraction(1, 3); + c[ 5] = fraction(1, 4); + c[ 6] = fraction(4, 13); + c[ 7] = fraction(127, 195); + c[ 8] = fraction(3, 5); + c[ 9] = fraction(6, 7); + c[10] = getField().getOne(); + c[11] = getField().getOne(); + c[12] = fraction(1.0, 10.0); + c[13] = fraction(1.0, 5.0); + c[14] = fraction(7.0, 9.0); + + return c; + + } + + /** {@inheritDoc} */ + public T[][] getA() { + + final T sqrt6 = getField().getOne().multiply(6).sqrt(); + + final T[][] a = MathArrays.buildArray(getField(), 15, -1); + for (int i = 0; i < a.length; ++i) { + a[i] = MathArrays.buildArray(getField(), i + 1); + } + + a[ 0][ 0] = sqrt6.add(-6).divide(-67.5); + + a[ 1][ 0] = sqrt6.add(-6).divide(-180); + a[ 1][ 1] = sqrt6.add(-6).divide( -60); + + a[ 2][ 0] = sqrt6.add(-6).divide(-120); + a[ 2][ 1] = getField().getZero(); + a[ 2][ 2] = sqrt6.add(-6).divide( -40); + + a[ 3][ 0] = sqrt6.multiply(107).add(462).divide( 3000); + a[ 3][ 1] = getField().getZero(); + a[ 3][ 2] = sqrt6.multiply(197).add(402).divide(-1000); + a[ 3][ 3] = sqrt6.multiply( 73).add(168).divide( 375); + + a[ 4][ 0] = fraction(1, 27); + a[ 4][ 1] = getField().getZero(); + a[ 4][ 2] = getField().getZero(); + a[ 4][ 3] = sqrt6.add( 16).divide( 108); + a[ 4][ 4] = sqrt6.add(-16).divide(-108); + + a[ 5][ 0] = fraction(19, 512); + a[ 5][ 1] = getField().getZero(); + a[ 5][ 2] = getField().getZero(); + a[ 5][ 3] = sqrt6.multiply( 23).add(118).divide(1024); + a[ 5][ 4] = sqrt6.multiply(-23).add(118).divide(1024); + a[ 5][ 5] = fraction(-9, 512); + + a[ 6][ 0] = fraction(13772, 371293); + a[ 6][ 1] = getField().getZero(); + a[ 6][ 2] = getField().getZero(); + a[ 6][ 3] = sqrt6.multiply( 4784).add(51544).divide(371293); + a[ 6][ 4] = sqrt6.multiply(-4784).add(51544).divide(371293); + a[ 6][ 5] = fraction(-5688, 371293); + a[ 6][ 6] = fraction( 3072, 371293); + + a[ 7][ 0] = fraction(58656157643.0, 93983540625.0); + a[ 7][ 1] = getField().getZero(); + a[ 7][ 2] = getField().getZero(); + a[ 7][ 3] = sqrt6.multiply(-318801444819.0).add(-1324889724104.0).divide(626556937500.0); + a[ 7][ 4] = sqrt6.multiply( 318801444819.0).add(-1324889724104.0).divide(626556937500.0); + a[ 7][ 5] = fraction(96044563816.0, 3480871875.0); + a[ 7][ 6] = fraction(5682451879168.0, 281950621875.0); + a[ 7][ 7] = fraction(-165125654.0, 3796875.0); + + a[ 8][ 0] = fraction(8909899.0, 18653125.0); + a[ 8][ 1] = getField().getZero(); + a[ 8][ 2] = getField().getZero(); + a[ 8][ 3] = sqrt6.multiply(-1137963.0).add(-4521408.0).divide(2937500.0); + a[ 8][ 4] = sqrt6.multiply( 1137963.0).add(-4521408.0).divide(2937500.0); + a[ 8][ 5] = fraction(96663078.0, 4553125.0); + a[ 8][ 6] = fraction(2107245056.0, 137915625.0); + a[ 8][ 7] = fraction(-4913652016.0, 147609375.0); + a[ 8][ 8] = fraction(-78894270.0, 3880452869.0); + + a[ 9][ 0] = fraction(-20401265806.0, 21769653311.0); + a[ 9][ 1] = getField().getZero(); + a[ 9][ 2] = getField().getZero(); + a[ 9][ 3] = sqrt6.multiply( 94326.0).add(354216.0).divide(112847.0); + a[ 9][ 4] = sqrt6.multiply(-94326.0).add(354216.0).divide(112847.0); + a[ 9][ 5] = fraction(-43306765128.0, 5313852383.0); + a[ 9][ 6] = fraction(-20866708358144.0, 1126708119789.0); + a[ 9][ 7] = fraction(14886003438020.0, 654632330667.0); + a[ 9][ 8] = fraction(35290686222309375.0, 14152473387134411.0); + a[ 9][ 9] = fraction(-1477884375.0, 485066827.0); + + a[10][ 0] = fraction(39815761.0, 17514443.0); + a[10][ 1] = getField().getZero(); + a[10][ 2] = getField().getZero(); + a[10][ 3] = sqrt6.multiply(-960905.0).add(-3457480.0).divide(551636.0); + a[10][ 4] = sqrt6.multiply( 960905.0).add(-3457480.0).divide(551636.0); + a[10][ 5] = fraction(-844554132.0, 47026969.0); + a[10][ 6] = fraction(8444996352.0, 302158619.0); + a[10][ 7] = fraction(-2509602342.0, 877790785.0); + a[10][ 8] = fraction(-28388795297996250.0, 3199510091356783.0); + a[10][ 9] = fraction(226716250.0, 18341897.0); + a[10][10] = fraction(1371316744.0, 2131383595.0); + + // the following stage is both for interpolation and the first stage in next step + // (the coefficients are identical to the B array) + a[11][ 0] = fraction(104257.0, 1920240.0); + a[11][ 1] = getField().getZero(); + a[11][ 2] = getField().getZero(); + a[11][ 3] = getField().getZero(); + a[11][ 4] = getField().getZero(); + a[11][ 5] = fraction(3399327.0, 763840.0); + a[11][ 6] = fraction(66578432.0, 35198415.0); + a[11][ 7] = fraction(-1674902723.0, 288716400.0); + a[11][ 8] = fraction(54980371265625.0, 176692375811392.0); + a[11][ 9] = fraction(-734375.0, 4826304.0); + a[11][10] = fraction(171414593.0, 851261400.0); + a[11][11] = fraction(137909.0, 3084480.0); + + // the following stages are for interpolation only + a[12][ 0] = fraction( 13481885573.0, 240030000000.0); + a[12][ 1] = getField().getZero(); + a[12][ 2] = getField().getZero(); + a[12][ 3] = getField().getZero(); + a[12][ 4] = getField().getZero(); + a[12][ 5] = getField().getZero(); + a[12][ 6] = fraction( 139418837528.0, 549975234375.0); + a[12][ 7] = fraction( -11108320068443.0, 45111937500000.0); + a[12][ 8] = fraction(-1769651421925959.0, 14249385146080000.0); + a[12][ 9] = fraction( 57799439.0, 377055000.0); + a[12][10] = fraction( 793322643029.0, 96734250000000.0); + a[12][11] = fraction( 1458939311.0, 192780000000.0); + a[12][12] = fraction( -4149.0, 500000.0); + + a[13][ 0] = fraction( 1595561272731.0, 50120273500000.0); + a[13][ 1] = getField().getZero(); + a[13][ 2] = getField().getZero(); + a[13][ 3] = getField().getZero(); + a[13][ 4] = getField().getZero(); + a[13][ 5] = fraction( 975183916491.0, 34457688031250.0); + a[13][ 6] = fraction( 38492013932672.0, 718912673015625.0); + a[13][ 7] = fraction(-1114881286517557.0, 20298710767500000.0); + a[13][ 8] = getField().getZero(); + a[13][ 9] = getField().getZero(); + a[13][10] = fraction( -2538710946863.0, 23431227861250000.0); + a[13][11] = fraction( 8824659001.0, 23066716781250.0); + a[13][12] = fraction( -11518334563.0, 33831184612500.0); + a[13][13] = fraction( 1912306948.0, 13532473845.0); + + a[14][ 0] = fraction( -13613986967.0, 31741908048.0); + a[14][ 1] = getField().getZero(); + a[14][ 2] = getField().getZero(); + a[14][ 3] = getField().getZero(); + a[14][ 4] = getField().getZero(); + a[14][ 5] = fraction( -4755612631.0, 1012344804.0); + a[14][ 6] = fraction( 42939257944576.0, 5588559685701.0); + a[14][ 7] = fraction( 77881972900277.0, 19140370552944.0); + a[14][ 8] = fraction( 22719829234375.0, 63689648654052.0); + a[14][ 9] = getField().getZero(); + a[14][10] = getField().getZero(); + a[14][11] = getField().getZero(); + a[14][12] = fraction( -1199007803.0, 857031517296.0); + a[14][13] = fraction( 157882067000.0, 53564469831.0); + a[14][14] = fraction( -290468882375.0, 31741908048.0); + + return a; + + } + + /** {@inheritDoc} */ + public T[] getB() { + final T[] b = MathArrays.buildArray(getField(), 16); + b[ 0] = fraction(104257, 1920240); + b[ 1] = getField().getZero(); + b[ 2] = getField().getZero(); + b[ 3] = getField().getZero(); + b[ 4] = getField().getZero(); + b[ 5] = fraction( 3399327.0, 763840.0); + b[ 6] = fraction( 66578432.0, 35198415.0); + b[ 7] = fraction( -1674902723.0, 288716400.0); + b[ 8] = fraction( 54980371265625.0, 176692375811392.0); + b[ 9] = fraction( -734375.0, 4826304.0); + b[10] = fraction( 171414593.0, 851261400.0); + b[11] = fraction( 137909.0, 3084480.0); + b[12] = getField().getZero(); + b[13] = getField().getZero(); + b[14] = getField().getZero(); + b[15] = getField().getZero(); + return b; + } + + /** {@inheritDoc} */ + @Override + protected DormandPrince853FieldStepInterpolator<T> + createInterpolator(final boolean forward, T[][] yDotK, + final FieldODEStateAndDerivative<T> globalPreviousState, + final FieldODEStateAndDerivative<T> globalCurrentState, final FieldEquationsMapper<T> mapper) { + return new DormandPrince853FieldStepInterpolator<T>(getField(), forward, yDotK, + globalPreviousState, globalCurrentState, + globalPreviousState, globalCurrentState, + mapper); + } + + /** {@inheritDoc} */ + @Override + public int getOrder() { + return 8; + } + + /** {@inheritDoc} */ + @Override + protected T estimateError(final T[][] yDotK, final T[] y0, final T[] y1, final T h) { + T error1 = h.getField().getZero(); + T error2 = h.getField().getZero(); + + for (int j = 0; j < mainSetDimension; ++j) { + final T errSum1 = yDotK[ 0][j].multiply(e1_01). + add(yDotK[ 5][j].multiply(e1_06)). + add(yDotK[ 6][j].multiply(e1_07)). + add(yDotK[ 7][j].multiply(e1_08)). + add(yDotK[ 8][j].multiply(e1_09)). + add(yDotK[ 9][j].multiply(e1_10)). + add(yDotK[10][j].multiply(e1_11)). + add(yDotK[11][j].multiply(e1_12)); + final T errSum2 = yDotK[ 0][j].multiply(e2_01). + add(yDotK[ 5][j].multiply(e2_06)). + add(yDotK[ 6][j].multiply(e2_07)). + add(yDotK[ 7][j].multiply(e2_08)). + add(yDotK[ 8][j].multiply(e2_09)). + add(yDotK[ 9][j].multiply(e2_10)). + add(yDotK[10][j].multiply(e2_11)). + add(yDotK[11][j].multiply(e2_12)); + + final T yScale = MathUtils.max(y0[j].abs(), y1[j].abs()); + final T tol = vecAbsoluteTolerance == null ? + yScale.multiply(scalRelativeTolerance).add(scalAbsoluteTolerance) : + yScale.multiply(vecRelativeTolerance[j]).add(vecAbsoluteTolerance[j]); + final T ratio1 = errSum1.divide(tol); + error1 = error1.add(ratio1.multiply(ratio1)); + final T ratio2 = errSum2.divide(tol); + error2 = error2.add(ratio2.multiply(ratio2)); + } + + T den = error1.add(error2.multiply(0.01)); + if (den.getReal() <= 0.0) { + den = h.getField().getOne(); + } + + return h.abs().multiply(error1).divide(den.multiply(mainSetDimension).sqrt()); + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince853FieldStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince853FieldStepInterpolator.java new file mode 100644 index 0000000..c706e2a --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince853FieldStepInterpolator.java @@ -0,0 +1,302 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.Field; +import org.apache.commons.math3.RealFieldElement; +import org.apache.commons.math3.exception.MaxCountExceededException; +import org.apache.commons.math3.ode.FieldEquationsMapper; +import org.apache.commons.math3.ode.FieldODEStateAndDerivative; +import org.apache.commons.math3.util.MathArrays; + +/** + * This class represents an interpolator over the last step during an + * ODE integration for the 8(5,3) Dormand-Prince integrator. + * + * @see DormandPrince853FieldIntegrator + * + * @param <T> the type of the field elements + * @since 3.6 + */ + +class DormandPrince853FieldStepInterpolator<T extends RealFieldElement<T>> + extends RungeKuttaFieldStepInterpolator<T> { + + /** Interpolation weights. + * (beware that only the non-null values are in the table) + */ + private final T[][] d; + + /** Simple constructor. + * @param field field to which the time and state vector elements belong + * @param forward integration direction indicator + * @param yDotK slopes at the intermediate points + * @param globalPreviousState start of the global step + * @param globalCurrentState end of the global step + * @param softPreviousState start of the restricted step + * @param softCurrentState end of the restricted step + * @param mapper equations mapper for the all equations + */ + DormandPrince853FieldStepInterpolator(final Field<T> field, final boolean forward, + final T[][] yDotK, + final FieldODEStateAndDerivative<T> globalPreviousState, + final FieldODEStateAndDerivative<T> globalCurrentState, + final FieldODEStateAndDerivative<T> softPreviousState, + final FieldODEStateAndDerivative<T> softCurrentState, + final FieldEquationsMapper<T> mapper) { + super(field, forward, yDotK, + globalPreviousState, globalCurrentState, softPreviousState, softCurrentState, + mapper); + // interpolation weights + d = MathArrays.buildArray(field, 7, 16); + + // this row is the same as the b array + d[0][ 0] = fraction(field, 104257, 1920240); + d[0][ 1] = field.getZero(); + d[0][ 2] = field.getZero(); + d[0][ 3] = field.getZero(); + d[0][ 4] = field.getZero(); + d[0][ 5] = fraction(field, 3399327.0, 763840.0); + d[0][ 6] = fraction(field, 66578432.0, 35198415.0); + d[0][ 7] = fraction(field, -1674902723.0, 288716400.0); + d[0][ 8] = fraction(field, 54980371265625.0, 176692375811392.0); + d[0][ 9] = fraction(field, -734375.0, 4826304.0); + d[0][10] = fraction(field, 171414593.0, 851261400.0); + d[0][11] = fraction(field, 137909.0, 3084480.0); + d[0][12] = field.getZero(); + d[0][13] = field.getZero(); + d[0][14] = field.getZero(); + d[0][15] = field.getZero(); + + d[1][ 0] = d[0][ 0].negate().add(1); + d[1][ 1] = d[0][ 1].negate(); + d[1][ 2] = d[0][ 2].negate(); + d[1][ 3] = d[0][ 3].negate(); + d[1][ 4] = d[0][ 4].negate(); + d[1][ 5] = d[0][ 5].negate(); + d[1][ 6] = d[0][ 6].negate(); + d[1][ 7] = d[0][ 7].negate(); + d[1][ 8] = d[0][ 8].negate(); + d[1][ 9] = d[0][ 9].negate(); + d[1][10] = d[0][10].negate(); + d[1][11] = d[0][11].negate(); + d[1][12] = d[0][12].negate(); // really 0 + d[1][13] = d[0][13].negate(); // really 0 + d[1][14] = d[0][14].negate(); // really 0 + d[1][15] = d[0][15].negate(); // really 0 + + d[2][ 0] = d[0][ 0].multiply(2).subtract(1); + d[2][ 1] = d[0][ 1].multiply(2); + d[2][ 2] = d[0][ 2].multiply(2); + d[2][ 3] = d[0][ 3].multiply(2); + d[2][ 4] = d[0][ 4].multiply(2); + d[2][ 5] = d[0][ 5].multiply(2); + d[2][ 6] = d[0][ 6].multiply(2); + d[2][ 7] = d[0][ 7].multiply(2); + d[2][ 8] = d[0][ 8].multiply(2); + d[2][ 9] = d[0][ 9].multiply(2); + d[2][10] = d[0][10].multiply(2); + d[2][11] = d[0][11].multiply(2); + d[2][12] = d[0][12].multiply(2).subtract(1); // really -1 + d[2][13] = d[0][13].multiply(2); // really 0 + d[2][14] = d[0][14].multiply(2); // really 0 + d[2][15] = d[0][15].multiply(2); // really 0 + + d[3][ 0] = fraction(field, -17751989329.0, 2106076560.0); + d[3][ 1] = field.getZero(); + d[3][ 2] = field.getZero(); + d[3][ 3] = field.getZero(); + d[3][ 4] = field.getZero(); + d[3][ 5] = fraction(field, 4272954039.0, 7539864640.0); + d[3][ 6] = fraction(field, -118476319744.0, 38604839385.0); + d[3][ 7] = fraction(field, 755123450731.0, 316657731600.0); + d[3][ 8] = fraction(field, 3692384461234828125.0, 1744130441634250432.0); + d[3][ 9] = fraction(field, -4612609375.0, 5293382976.0); + d[3][10] = fraction(field, 2091772278379.0, 933644586600.0); + d[3][11] = fraction(field, 2136624137.0, 3382989120.0); + d[3][12] = fraction(field, -126493.0, 1421424.0); + d[3][13] = fraction(field, 98350000.0, 5419179.0); + d[3][14] = fraction(field, -18878125.0, 2053168.0); + d[3][15] = fraction(field, -1944542619.0, 438351368.0); + + d[4][ 0] = fraction(field, 32941697297.0, 3159114840.0); + d[4][ 1] = field.getZero(); + d[4][ 2] = field.getZero(); + d[4][ 3] = field.getZero(); + d[4][ 4] = field.getZero(); + d[4][ 5] = fraction(field, 456696183123.0, 1884966160.0); + d[4][ 6] = fraction(field, 19132610714624.0, 115814518155.0); + d[4][ 7] = fraction(field, -177904688592943.0, 474986597400.0); + d[4][ 8] = fraction(field, -4821139941836765625.0, 218016305204281304.0); + d[4][ 9] = fraction(field, 30702015625.0, 3970037232.0); + d[4][10] = fraction(field, -85916079474274.0, 2800933759800.0); + d[4][11] = fraction(field, -5919468007.0, 634310460.0); + d[4][12] = fraction(field, 2479159.0, 157936.0); + d[4][13] = fraction(field, -18750000.0, 602131.0); + d[4][14] = fraction(field, -19203125.0, 2053168.0); + d[4][15] = fraction(field, 15700361463.0, 438351368.0); + + d[5][ 0] = fraction(field, 12627015655.0, 631822968.0); + d[5][ 1] = field.getZero(); + d[5][ 2] = field.getZero(); + d[5][ 3] = field.getZero(); + d[5][ 4] = field.getZero(); + d[5][ 5] = fraction(field, -72955222965.0, 188496616.0); + d[5][ 6] = fraction(field, -13145744952320.0, 69488710893.0); + d[5][ 7] = fraction(field, 30084216194513.0, 56998391688.0); + d[5][ 8] = fraction(field, -296858761006640625.0, 25648977082856624.0); + d[5][ 9] = fraction(field, 569140625.0, 82709109.0); + d[5][10] = fraction(field, -18684190637.0, 18672891732.0); + d[5][11] = fraction(field, 69644045.0, 89549712.0); + d[5][12] = fraction(field, -11847025.0, 4264272.0); + d[5][13] = fraction(field, -978650000.0, 16257537.0); + d[5][14] = fraction(field, 519371875.0, 6159504.0); + d[5][15] = fraction(field, 5256837225.0, 438351368.0); + + d[6][ 0] = fraction(field, -450944925.0, 17550638.0); + d[6][ 1] = field.getZero(); + d[6][ 2] = field.getZero(); + d[6][ 3] = field.getZero(); + d[6][ 4] = field.getZero(); + d[6][ 5] = fraction(field, -14532122925.0, 94248308.0); + d[6][ 6] = fraction(field, -595876966400.0, 2573655959.0); + d[6][ 7] = fraction(field, 188748653015.0, 527762886.0); + d[6][ 8] = fraction(field, 2545485458115234375.0, 27252038150535163.0); + d[6][ 9] = fraction(field, -1376953125.0, 36759604.0); + d[6][10] = fraction(field, 53995596795.0, 518691437.0); + d[6][11] = fraction(field, 210311225.0, 7047894.0); + d[6][12] = fraction(field, -1718875.0, 39484.0); + d[6][13] = fraction(field, 58000000.0, 602131.0); + d[6][14] = fraction(field, -1546875.0, 39484.0); + d[6][15] = fraction(field, -1262172375.0, 8429834.0); + + } + + /** {@inheritDoc} */ + @Override + protected DormandPrince853FieldStepInterpolator<T> create(final Field<T> newField, final boolean newForward, final T[][] newYDotK, + final FieldODEStateAndDerivative<T> newGlobalPreviousState, + final FieldODEStateAndDerivative<T> newGlobalCurrentState, + final FieldODEStateAndDerivative<T> newSoftPreviousState, + final FieldODEStateAndDerivative<T> newSoftCurrentState, + final FieldEquationsMapper<T> newMapper) { + return new DormandPrince853FieldStepInterpolator<T>(newField, newForward, newYDotK, + newGlobalPreviousState, newGlobalCurrentState, + newSoftPreviousState, newSoftCurrentState, + newMapper); + } + + /** Create a fraction. + * @param field field to which the elements belong + * @param p numerator + * @param q denominator + * @return p/q computed in the instance field + */ + private T fraction(final Field<T> field, final double p, final double q) { + return field.getZero().add(p).divide(q); + } + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override + protected FieldODEStateAndDerivative<T> computeInterpolatedStateAndDerivatives(final FieldEquationsMapper<T> mapper, + final T time, final T theta, + final T thetaH, final T oneMinusThetaH) + throws MaxCountExceededException { + + final T one = time.getField().getOne(); + final T eta = one.subtract(theta); + final T twoTheta = theta.multiply(2); + final T theta2 = theta.multiply(theta); + final T dot1 = one.subtract(twoTheta); + final T dot2 = theta.multiply(theta.multiply(-3).add(2)); + final T dot3 = twoTheta.multiply(theta.multiply(twoTheta.subtract(3)).add(1)); + final T dot4 = theta2.multiply(theta.multiply(theta.multiply(5).subtract(8)).add(3)); + final T dot5 = theta2.multiply(theta.multiply(theta.multiply(theta.multiply(-6).add(15)).subtract(12)).add(3)); + final T dot6 = theta2.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply(-7).add(18)).subtract(15)).add(4))); + final T[] interpolatedState; + final T[] interpolatedDerivatives; + + + if (getGlobalPreviousState() != null && theta.getReal() <= 0.5) { + final T f0 = thetaH; + final T f1 = f0.multiply(eta); + final T f2 = f1.multiply(theta); + final T f3 = f2.multiply(eta); + final T f4 = f3.multiply(theta); + final T f5 = f4.multiply(eta); + final T f6 = f5.multiply(theta); + final T[] p = MathArrays.buildArray(time.getField(), 16); + final T[] q = MathArrays.buildArray(time.getField(), 16); + for (int i = 0; i < p.length; ++i) { + p[i] = f0.multiply(d[0][i]). + add(f1.multiply(d[1][i])). + add(f2.multiply(d[2][i])). + add(f3.multiply(d[3][i])). + add(f4.multiply(d[4][i])). + add(f5.multiply(d[5][i])). + add(f6.multiply(d[6][i])); + q[i] = d[0][i]. + add(dot1.multiply(d[1][i])). + add(dot2.multiply(d[2][i])). + add(dot3.multiply(d[3][i])). + add(dot4.multiply(d[4][i])). + add(dot5.multiply(d[5][i])). + add(dot6.multiply(d[6][i])); + } + interpolatedState = previousStateLinearCombination(p[0], p[1], p[ 2], p[ 3], p[ 4], p[ 5], p[ 6], p[ 7], + p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); + interpolatedDerivatives = derivativeLinearCombination(q[0], q[1], q[ 2], q[ 3], q[ 4], q[ 5], q[ 6], q[ 7], + q[8], q[9], q[10], q[11], q[12], q[13], q[14], q[15]); + } else { + final T f0 = oneMinusThetaH.negate(); + final T f1 = f0.multiply(theta).negate(); + final T f2 = f1.multiply(theta); + final T f3 = f2.multiply(eta); + final T f4 = f3.multiply(theta); + final T f5 = f4.multiply(eta); + final T f6 = f5.multiply(theta); + final T[] p = MathArrays.buildArray(time.getField(), 16); + final T[] q = MathArrays.buildArray(time.getField(), 16); + for (int i = 0; i < p.length; ++i) { + p[i] = f0.multiply(d[0][i]). + add(f1.multiply(d[1][i])). + add(f2.multiply(d[2][i])). + add(f3.multiply(d[3][i])). + add(f4.multiply(d[4][i])). + add(f5.multiply(d[5][i])). + add(f6.multiply(d[6][i])); + q[i] = d[0][i]. + add(dot1.multiply(d[1][i])). + add(dot2.multiply(d[2][i])). + add(dot3.multiply(d[3][i])). + add(dot4.multiply(d[4][i])). + add(dot5.multiply(d[5][i])). + add(dot6.multiply(d[6][i])); + } + interpolatedState = currentStateLinearCombination(p[0], p[1], p[ 2], p[ 3], p[ 4], p[ 5], p[ 6], p[ 7], + p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); + interpolatedDerivatives = derivativeLinearCombination(q[0], q[1], q[ 2], q[ 3], q[ 4], q[ 5], q[ 6], q[ 7], + q[8], q[9], q[10], q[11], q[12], q[13], q[14], q[15]); + } + + return new FieldODEStateAndDerivative<T>(time, interpolatedState, interpolatedDerivatives); + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince853Integrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince853Integrator.java new file mode 100644 index 0000000..895cb88 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince853Integrator.java @@ -0,0 +1,286 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.util.FastMath; + + +/** + * This class implements the 8(5,3) Dormand-Prince integrator for Ordinary + * Differential Equations. + * + * <p>This integrator is an embedded Runge-Kutta integrator + * of order 8(5,3) used in local extrapolation mode (i.e. the solution + * is computed using the high order formula) with stepsize control + * (and automatic step initialization) and continuous output. This + * method uses 12 functions evaluations per step for integration and 4 + * evaluations for interpolation. However, since the first + * interpolation evaluation is the same as the first integration + * evaluation of the next step, we have included it in the integrator + * rather than in the interpolator and specified the method was an + * <i>fsal</i>. Hence, despite we have 13 stages here, the cost is + * really 12 evaluations per step even if no interpolation is done, + * and the overcost of interpolation is only 3 evaluations.</p> + * + * <p>This method is based on an 8(6) method by Dormand and Prince + * (i.e. order 8 for the integration and order 6 for error estimation) + * modified by Hairer and Wanner to use a 5th order error estimator + * with 3rd order correction. This modification was introduced because + * the original method failed in some cases (wrong steps can be + * accepted when step size is too large, for example in the + * Brusselator problem) and also had <i>severe difficulties when + * applied to problems with discontinuities</i>. This modification is + * explained in the second edition of the first volume (Nonstiff + * Problems) of the reference book by Hairer, Norsett and Wanner: + * <i>Solving Ordinary Differential Equations</i> (Springer-Verlag, + * ISBN 3-540-56670-8).</p> + * + * @since 1.2 + */ + +public class DormandPrince853Integrator extends EmbeddedRungeKuttaIntegrator { + + /** Integrator method name. */ + private static final String METHOD_NAME = "Dormand-Prince 8 (5, 3)"; + + /** Time steps Butcher array. */ + private static final double[] STATIC_C = { + (12.0 - 2.0 * FastMath.sqrt(6.0)) / 135.0, (6.0 - FastMath.sqrt(6.0)) / 45.0, (6.0 - FastMath.sqrt(6.0)) / 30.0, + (6.0 + FastMath.sqrt(6.0)) / 30.0, 1.0/3.0, 1.0/4.0, 4.0/13.0, 127.0/195.0, 3.0/5.0, + 6.0/7.0, 1.0, 1.0 + }; + + /** Internal weights Butcher array. */ + private static final double[][] STATIC_A = { + + // k2 + {(12.0 - 2.0 * FastMath.sqrt(6.0)) / 135.0}, + + // k3 + {(6.0 - FastMath.sqrt(6.0)) / 180.0, (6.0 - FastMath.sqrt(6.0)) / 60.0}, + + // k4 + {(6.0 - FastMath.sqrt(6.0)) / 120.0, 0.0, (6.0 - FastMath.sqrt(6.0)) / 40.0}, + + // k5 + {(462.0 + 107.0 * FastMath.sqrt(6.0)) / 3000.0, 0.0, + (-402.0 - 197.0 * FastMath.sqrt(6.0)) / 1000.0, (168.0 + 73.0 * FastMath.sqrt(6.0)) / 375.0}, + + // k6 + {1.0 / 27.0, 0.0, 0.0, (16.0 + FastMath.sqrt(6.0)) / 108.0, (16.0 - FastMath.sqrt(6.0)) / 108.0}, + + // k7 + {19.0 / 512.0, 0.0, 0.0, (118.0 + 23.0 * FastMath.sqrt(6.0)) / 1024.0, + (118.0 - 23.0 * FastMath.sqrt(6.0)) / 1024.0, -9.0 / 512.0}, + + // k8 + {13772.0 / 371293.0, 0.0, 0.0, (51544.0 + 4784.0 * FastMath.sqrt(6.0)) / 371293.0, + (51544.0 - 4784.0 * FastMath.sqrt(6.0)) / 371293.0, -5688.0 / 371293.0, 3072.0 / 371293.0}, + + // k9 + {58656157643.0 / 93983540625.0, 0.0, 0.0, + (-1324889724104.0 - 318801444819.0 * FastMath.sqrt(6.0)) / 626556937500.0, + (-1324889724104.0 + 318801444819.0 * FastMath.sqrt(6.0)) / 626556937500.0, + 96044563816.0 / 3480871875.0, 5682451879168.0 / 281950621875.0, + -165125654.0 / 3796875.0}, + + // k10 + {8909899.0 / 18653125.0, 0.0, 0.0, + (-4521408.0 - 1137963.0 * FastMath.sqrt(6.0)) / 2937500.0, + (-4521408.0 + 1137963.0 * FastMath.sqrt(6.0)) / 2937500.0, + 96663078.0 / 4553125.0, 2107245056.0 / 137915625.0, + -4913652016.0 / 147609375.0, -78894270.0 / 3880452869.0}, + + // k11 + {-20401265806.0 / 21769653311.0, 0.0, 0.0, + (354216.0 + 94326.0 * FastMath.sqrt(6.0)) / 112847.0, + (354216.0 - 94326.0 * FastMath.sqrt(6.0)) / 112847.0, + -43306765128.0 / 5313852383.0, -20866708358144.0 / 1126708119789.0, + 14886003438020.0 / 654632330667.0, 35290686222309375.0 / 14152473387134411.0, + -1477884375.0 / 485066827.0}, + + // k12 + {39815761.0 / 17514443.0, 0.0, 0.0, + (-3457480.0 - 960905.0 * FastMath.sqrt(6.0)) / 551636.0, + (-3457480.0 + 960905.0 * FastMath.sqrt(6.0)) / 551636.0, + -844554132.0 / 47026969.0, 8444996352.0 / 302158619.0, + -2509602342.0 / 877790785.0, -28388795297996250.0 / 3199510091356783.0, + 226716250.0 / 18341897.0, 1371316744.0 / 2131383595.0}, + + // k13 should be for interpolation only, but since it is the same + // stage as the first evaluation of the next step, we perform it + // here at no cost by specifying this is an fsal method + {104257.0/1920240.0, 0.0, 0.0, 0.0, 0.0, 3399327.0/763840.0, + 66578432.0/35198415.0, -1674902723.0/288716400.0, + 54980371265625.0/176692375811392.0, -734375.0/4826304.0, + 171414593.0/851261400.0, 137909.0/3084480.0} + + }; + + /** Propagation weights Butcher array. */ + private static final double[] STATIC_B = { + 104257.0/1920240.0, + 0.0, + 0.0, + 0.0, + 0.0, + 3399327.0/763840.0, + 66578432.0/35198415.0, + -1674902723.0/288716400.0, + 54980371265625.0/176692375811392.0, + -734375.0/4826304.0, + 171414593.0/851261400.0, + 137909.0/3084480.0, + 0.0 + }; + + /** First error weights array, element 1. */ + private static final double E1_01 = 116092271.0 / 8848465920.0; + + // elements 2 to 5 are zero, so they are neither stored nor used + + /** First error weights array, element 6. */ + private static final double E1_06 = -1871647.0 / 1527680.0; + + /** First error weights array, element 7. */ + private static final double E1_07 = -69799717.0 / 140793660.0; + + /** First error weights array, element 8. */ + private static final double E1_08 = 1230164450203.0 / 739113984000.0; + + /** First error weights array, element 9. */ + private static final double E1_09 = -1980813971228885.0 / 5654156025964544.0; + + /** First error weights array, element 10. */ + private static final double E1_10 = 464500805.0 / 1389975552.0; + + /** First error weights array, element 11. */ + private static final double E1_11 = 1606764981773.0 / 19613062656000.0; + + /** First error weights array, element 12. */ + private static final double E1_12 = -137909.0 / 6168960.0; + + + /** Second error weights array, element 1. */ + private static final double E2_01 = -364463.0 / 1920240.0; + + // elements 2 to 5 are zero, so they are neither stored nor used + + /** Second error weights array, element 6. */ + private static final double E2_06 = 3399327.0 / 763840.0; + + /** Second error weights array, element 7. */ + private static final double E2_07 = 66578432.0 / 35198415.0; + + /** Second error weights array, element 8. */ + private static final double E2_08 = -1674902723.0 / 288716400.0; + + /** Second error weights array, element 9. */ + private static final double E2_09 = -74684743568175.0 / 176692375811392.0; + + /** Second error weights array, element 10. */ + private static final double E2_10 = -734375.0 / 4826304.0; + + /** Second error weights array, element 11. */ + private static final double E2_11 = 171414593.0 / 851261400.0; + + /** Second error weights array, element 12. */ + private static final double E2_12 = 69869.0 / 3084480.0; + + /** Simple constructor. + * Build an eighth order Dormand-Prince integrator with the given step bounds + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + */ + public DormandPrince853Integrator(final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) { + super(METHOD_NAME, true, STATIC_C, STATIC_A, STATIC_B, + new DormandPrince853StepInterpolator(), + minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance); + } + + /** Simple constructor. + * Build an eighth order Dormand-Prince integrator with the given step bounds + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + */ + public DormandPrince853Integrator(final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) { + super(METHOD_NAME, true, STATIC_C, STATIC_A, STATIC_B, + new DormandPrince853StepInterpolator(), + minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance); + } + + /** {@inheritDoc} */ + @Override + public int getOrder() { + return 8; + } + + /** {@inheritDoc} */ + @Override + protected double estimateError(final double[][] yDotK, + final double[] y0, final double[] y1, + final double h) { + double error1 = 0; + double error2 = 0; + + for (int j = 0; j < mainSetDimension; ++j) { + final double errSum1 = E1_01 * yDotK[0][j] + E1_06 * yDotK[5][j] + + E1_07 * yDotK[6][j] + E1_08 * yDotK[7][j] + + E1_09 * yDotK[8][j] + E1_10 * yDotK[9][j] + + E1_11 * yDotK[10][j] + E1_12 * yDotK[11][j]; + final double errSum2 = E2_01 * yDotK[0][j] + E2_06 * yDotK[5][j] + + E2_07 * yDotK[6][j] + E2_08 * yDotK[7][j] + + E2_09 * yDotK[8][j] + E2_10 * yDotK[9][j] + + E2_11 * yDotK[10][j] + E2_12 * yDotK[11][j]; + + final double yScale = FastMath.max(FastMath.abs(y0[j]), FastMath.abs(y1[j])); + final double tol = (vecAbsoluteTolerance == null) ? + (scalAbsoluteTolerance + scalRelativeTolerance * yScale) : + (vecAbsoluteTolerance[j] + vecRelativeTolerance[j] * yScale); + final double ratio1 = errSum1 / tol; + error1 += ratio1 * ratio1; + final double ratio2 = errSum2 / tol; + error2 += ratio2 * ratio2; + } + + double den = error1 + 0.01 * error2; + if (den <= 0.0) { + den = 1.0; + } + + return FastMath.abs(h) * error1 / FastMath.sqrt(mainSetDimension * den); + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince853StepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince853StepInterpolator.java new file mode 100644 index 0000000..6d6ff6c --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince853StepInterpolator.java @@ -0,0 +1,501 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +import org.apache.commons.math3.exception.MaxCountExceededException; +import org.apache.commons.math3.ode.AbstractIntegrator; +import org.apache.commons.math3.ode.EquationsMapper; +import org.apache.commons.math3.ode.sampling.StepInterpolator; + +/** + * This class represents an interpolator over the last step during an + * ODE integration for the 8(5,3) Dormand-Prince integrator. + * + * @see DormandPrince853Integrator + * + * @since 1.2 + */ + +class DormandPrince853StepInterpolator + extends RungeKuttaStepInterpolator { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 20111120L; + + /** Propagation weights, element 1. */ + private static final double B_01 = 104257.0 / 1920240.0; + + // elements 2 to 5 are zero, so they are neither stored nor used + + /** Propagation weights, element 6. */ + private static final double B_06 = 3399327.0 / 763840.0; + + /** Propagation weights, element 7. */ + private static final double B_07 = 66578432.0 / 35198415.0; + + /** Propagation weights, element 8. */ + private static final double B_08 = -1674902723.0 / 288716400.0; + + /** Propagation weights, element 9. */ + private static final double B_09 = 54980371265625.0 / 176692375811392.0; + + /** Propagation weights, element 10. */ + private static final double B_10 = -734375.0 / 4826304.0; + + /** Propagation weights, element 11. */ + private static final double B_11 = 171414593.0 / 851261400.0; + + /** Propagation weights, element 12. */ + private static final double B_12 = 137909.0 / 3084480.0; + + /** Time step for stage 14 (interpolation only). */ + private static final double C14 = 1.0 / 10.0; + + /** Internal weights for stage 14, element 1. */ + private static final double K14_01 = 13481885573.0 / 240030000000.0 - B_01; + + // elements 2 to 5 are zero, so they are neither stored nor used + + /** Internal weights for stage 14, element 6. */ + private static final double K14_06 = 0.0 - B_06; + + /** Internal weights for stage 14, element 7. */ + private static final double K14_07 = 139418837528.0 / 549975234375.0 - B_07; + + /** Internal weights for stage 14, element 8. */ + private static final double K14_08 = -11108320068443.0 / 45111937500000.0 - B_08; + + /** Internal weights for stage 14, element 9. */ + private static final double K14_09 = -1769651421925959.0 / 14249385146080000.0 - B_09; + + /** Internal weights for stage 14, element 10. */ + private static final double K14_10 = 57799439.0 / 377055000.0 - B_10; + + /** Internal weights for stage 14, element 11. */ + private static final double K14_11 = 793322643029.0 / 96734250000000.0 - B_11; + + /** Internal weights for stage 14, element 12. */ + private static final double K14_12 = 1458939311.0 / 192780000000.0 - B_12; + + /** Internal weights for stage 14, element 13. */ + private static final double K14_13 = -4149.0 / 500000.0; + + /** Time step for stage 15 (interpolation only). */ + private static final double C15 = 1.0 / 5.0; + + + /** Internal weights for stage 15, element 1. */ + private static final double K15_01 = 1595561272731.0 / 50120273500000.0 - B_01; + + // elements 2 to 5 are zero, so they are neither stored nor used + + /** Internal weights for stage 15, element 6. */ + private static final double K15_06 = 975183916491.0 / 34457688031250.0 - B_06; + + /** Internal weights for stage 15, element 7. */ + private static final double K15_07 = 38492013932672.0 / 718912673015625.0 - B_07; + + /** Internal weights for stage 15, element 8. */ + private static final double K15_08 = -1114881286517557.0 / 20298710767500000.0 - B_08; + + /** Internal weights for stage 15, element 9. */ + private static final double K15_09 = 0.0 - B_09; + + /** Internal weights for stage 15, element 10. */ + private static final double K15_10 = 0.0 - B_10; + + /** Internal weights for stage 15, element 11. */ + private static final double K15_11 = -2538710946863.0 / 23431227861250000.0 - B_11; + + /** Internal weights for stage 15, element 12. */ + private static final double K15_12 = 8824659001.0 / 23066716781250.0 - B_12; + + /** Internal weights for stage 15, element 13. */ + private static final double K15_13 = -11518334563.0 / 33831184612500.0; + + /** Internal weights for stage 15, element 14. */ + private static final double K15_14 = 1912306948.0 / 13532473845.0; + + /** Time step for stage 16 (interpolation only). */ + private static final double C16 = 7.0 / 9.0; + + + /** Internal weights for stage 16, element 1. */ + private static final double K16_01 = -13613986967.0 / 31741908048.0 - B_01; + + // elements 2 to 5 are zero, so they are neither stored nor used + + /** Internal weights for stage 16, element 6. */ + private static final double K16_06 = -4755612631.0 / 1012344804.0 - B_06; + + /** Internal weights for stage 16, element 7. */ + private static final double K16_07 = 42939257944576.0 / 5588559685701.0 - B_07; + + /** Internal weights for stage 16, element 8. */ + private static final double K16_08 = 77881972900277.0 / 19140370552944.0 - B_08; + + /** Internal weights for stage 16, element 9. */ + private static final double K16_09 = 22719829234375.0 / 63689648654052.0 - B_09; + + /** Internal weights for stage 16, element 10. */ + private static final double K16_10 = 0.0 - B_10; + + /** Internal weights for stage 16, element 11. */ + private static final double K16_11 = 0.0 - B_11; + + /** Internal weights for stage 16, element 12. */ + private static final double K16_12 = 0.0 - B_12; + + /** Internal weights for stage 16, element 13. */ + private static final double K16_13 = -1199007803.0 / 857031517296.0; + + /** Internal weights for stage 16, element 14. */ + private static final double K16_14 = 157882067000.0 / 53564469831.0; + + /** Internal weights for stage 16, element 15. */ + private static final double K16_15 = -290468882375.0 / 31741908048.0; + + /** Interpolation weights. + * (beware that only the non-null values are in the table) + */ + private static final double[][] D = { + + { -17751989329.0 / 2106076560.0, 4272954039.0 / 7539864640.0, + -118476319744.0 / 38604839385.0, 755123450731.0 / 316657731600.0, + 3692384461234828125.0 / 1744130441634250432.0, -4612609375.0 / 5293382976.0, + 2091772278379.0 / 933644586600.0, 2136624137.0 / 3382989120.0, + -126493.0 / 1421424.0, 98350000.0 / 5419179.0, + -18878125.0 / 2053168.0, -1944542619.0 / 438351368.0}, + + { 32941697297.0 / 3159114840.0, 456696183123.0 / 1884966160.0, + 19132610714624.0 / 115814518155.0, -177904688592943.0 / 474986597400.0, + -4821139941836765625.0 / 218016305204281304.0, 30702015625.0 / 3970037232.0, + -85916079474274.0 / 2800933759800.0, -5919468007.0 / 634310460.0, + 2479159.0 / 157936.0, -18750000.0 / 602131.0, + -19203125.0 / 2053168.0, 15700361463.0 / 438351368.0}, + + { 12627015655.0 / 631822968.0, -72955222965.0 / 188496616.0, + -13145744952320.0 / 69488710893.0, 30084216194513.0 / 56998391688.0, + -296858761006640625.0 / 25648977082856624.0, 569140625.0 / 82709109.0, + -18684190637.0 / 18672891732.0, 69644045.0 / 89549712.0, + -11847025.0 / 4264272.0, -978650000.0 / 16257537.0, + 519371875.0 / 6159504.0, 5256837225.0 / 438351368.0}, + + { -450944925.0 / 17550638.0, -14532122925.0 / 94248308.0, + -595876966400.0 / 2573655959.0, 188748653015.0 / 527762886.0, + 2545485458115234375.0 / 27252038150535163.0, -1376953125.0 / 36759604.0, + 53995596795.0 / 518691437.0, 210311225.0 / 7047894.0, + -1718875.0 / 39484.0, 58000000.0 / 602131.0, + -1546875.0 / 39484.0, -1262172375.0 / 8429834.0} + + }; + + /** Last evaluations. */ + private double[][] yDotKLast; + + /** Vectors for interpolation. */ + private double[][] v; + + /** Initialization indicator for the interpolation vectors. */ + private boolean vectorsInitialized; + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link #reinitialize} method should be called before using the + * instance in order to initialize the internal arrays. This + * constructor is used only in order to delay the initialization in + * some cases. The {@link EmbeddedRungeKuttaIntegrator} uses the + * prototyping design pattern to create the step interpolators by + * cloning an uninitialized model and latter initializing the copy. + */ + // CHECKSTYLE: stop RedundantModifier + // the public modifier here is needed for serialization + public DormandPrince853StepInterpolator() { + super(); + yDotKLast = null; + v = null; + vectorsInitialized = false; + } + // CHECKSTYLE: resume RedundantModifier + + /** Copy constructor. + * @param interpolator interpolator to copy from. The copy is a deep + * copy: its arrays are separated from the original arrays of the + * instance + */ + DormandPrince853StepInterpolator(final DormandPrince853StepInterpolator interpolator) { + + super(interpolator); + + if (interpolator.currentState == null) { + + yDotKLast = null; + v = null; + vectorsInitialized = false; + + } else { + + final int dimension = interpolator.currentState.length; + + yDotKLast = new double[3][]; + for (int k = 0; k < yDotKLast.length; ++k) { + yDotKLast[k] = new double[dimension]; + System.arraycopy(interpolator.yDotKLast[k], 0, yDotKLast[k], 0, + dimension); + } + + v = new double[7][]; + for (int k = 0; k < v.length; ++k) { + v[k] = new double[dimension]; + System.arraycopy(interpolator.v[k], 0, v[k], 0, dimension); + } + + vectorsInitialized = interpolator.vectorsInitialized; + + } + + } + + /** {@inheritDoc} */ + @Override + protected StepInterpolator doCopy() { + return new DormandPrince853StepInterpolator(this); + } + + /** {@inheritDoc} */ + @Override + public void reinitialize(final AbstractIntegrator integrator, + final double[] y, final double[][] yDotK, final boolean forward, + final EquationsMapper primaryMapper, + final EquationsMapper[] secondaryMappers) { + + super.reinitialize(integrator, y, yDotK, forward, primaryMapper, secondaryMappers); + + final int dimension = currentState.length; + + yDotKLast = new double[3][]; + for (int k = 0; k < yDotKLast.length; ++k) { + yDotKLast[k] = new double[dimension]; + } + + v = new double[7][]; + for (int k = 0; k < v.length; ++k) { + v[k] = new double[dimension]; + } + + vectorsInitialized = false; + + } + + /** {@inheritDoc} */ + @Override + public void storeTime(final double t) { + super.storeTime(t); + vectorsInitialized = false; + } + + /** {@inheritDoc} */ + @Override + protected void computeInterpolatedStateAndDerivatives(final double theta, + final double oneMinusThetaH) + throws MaxCountExceededException { + + if (! vectorsInitialized) { + + if (v == null) { + v = new double[7][]; + for (int k = 0; k < 7; ++k) { + v[k] = new double[interpolatedState.length]; + } + } + + // perform the last evaluations if they have not been done yet + finalizeStep(); + + // compute the interpolation vectors for this time step + for (int i = 0; i < interpolatedState.length; ++i) { + final double yDot1 = yDotK[0][i]; + final double yDot6 = yDotK[5][i]; + final double yDot7 = yDotK[6][i]; + final double yDot8 = yDotK[7][i]; + final double yDot9 = yDotK[8][i]; + final double yDot10 = yDotK[9][i]; + final double yDot11 = yDotK[10][i]; + final double yDot12 = yDotK[11][i]; + final double yDot13 = yDotK[12][i]; + final double yDot14 = yDotKLast[0][i]; + final double yDot15 = yDotKLast[1][i]; + final double yDot16 = yDotKLast[2][i]; + v[0][i] = B_01 * yDot1 + B_06 * yDot6 + B_07 * yDot7 + + B_08 * yDot8 + B_09 * yDot9 + B_10 * yDot10 + + B_11 * yDot11 + B_12 * yDot12; + v[1][i] = yDot1 - v[0][i]; + v[2][i] = v[0][i] - v[1][i] - yDotK[12][i]; + for (int k = 0; k < D.length; ++k) { + v[k+3][i] = D[k][0] * yDot1 + D[k][1] * yDot6 + D[k][2] * yDot7 + + D[k][3] * yDot8 + D[k][4] * yDot9 + D[k][5] * yDot10 + + D[k][6] * yDot11 + D[k][7] * yDot12 + D[k][8] * yDot13 + + D[k][9] * yDot14 + D[k][10] * yDot15 + D[k][11] * yDot16; + } + } + + vectorsInitialized = true; + + } + + final double eta = 1 - theta; + final double twoTheta = 2 * theta; + final double theta2 = theta * theta; + final double dot1 = 1 - twoTheta; + final double dot2 = theta * (2 - 3 * theta); + final double dot3 = twoTheta * (1 + theta * (twoTheta -3)); + final double dot4 = theta2 * (3 + theta * (5 * theta - 8)); + final double dot5 = theta2 * (3 + theta * (-12 + theta * (15 - 6 * theta))); + final double dot6 = theta2 * theta * (4 + theta * (-15 + theta * (18 - 7 * theta))); + + if ((previousState != null) && (theta <= 0.5)) { + for (int i = 0; i < interpolatedState.length; ++i) { + interpolatedState[i] = previousState[i] + + theta * h * (v[0][i] + + eta * (v[1][i] + + theta * (v[2][i] + + eta * (v[3][i] + + theta * (v[4][i] + + eta * (v[5][i] + + theta * (v[6][i]))))))); + interpolatedDerivatives[i] = v[0][i] + dot1 * v[1][i] + dot2 * v[2][i] + + dot3 * v[3][i] + dot4 * v[4][i] + + dot5 * v[5][i] + dot6 * v[6][i]; + } + } else { + for (int i = 0; i < interpolatedState.length; ++i) { + interpolatedState[i] = currentState[i] - + oneMinusThetaH * (v[0][i] - + theta * (v[1][i] + + theta * (v[2][i] + + eta * (v[3][i] + + theta * (v[4][i] + + eta * (v[5][i] + + theta * (v[6][i]))))))); + interpolatedDerivatives[i] = v[0][i] + dot1 * v[1][i] + dot2 * v[2][i] + + dot3 * v[3][i] + dot4 * v[4][i] + + dot5 * v[5][i] + dot6 * v[6][i]; + } + } + + } + + /** {@inheritDoc} */ + @Override + protected void doFinalize() throws MaxCountExceededException { + + if (currentState == null) { + // we are finalizing an uninitialized instance + return; + } + + double s; + final double[] yTmp = new double[currentState.length]; + final double pT = getGlobalPreviousTime(); + + // k14 + for (int j = 0; j < currentState.length; ++j) { + s = K14_01 * yDotK[0][j] + K14_06 * yDotK[5][j] + K14_07 * yDotK[6][j] + + K14_08 * yDotK[7][j] + K14_09 * yDotK[8][j] + K14_10 * yDotK[9][j] + + K14_11 * yDotK[10][j] + K14_12 * yDotK[11][j] + K14_13 * yDotK[12][j]; + yTmp[j] = currentState[j] + h * s; + } + integrator.computeDerivatives(pT + C14 * h, yTmp, yDotKLast[0]); + + // k15 + for (int j = 0; j < currentState.length; ++j) { + s = K15_01 * yDotK[0][j] + K15_06 * yDotK[5][j] + K15_07 * yDotK[6][j] + + K15_08 * yDotK[7][j] + K15_09 * yDotK[8][j] + K15_10 * yDotK[9][j] + + K15_11 * yDotK[10][j] + K15_12 * yDotK[11][j] + K15_13 * yDotK[12][j] + + K15_14 * yDotKLast[0][j]; + yTmp[j] = currentState[j] + h * s; + } + integrator.computeDerivatives(pT + C15 * h, yTmp, yDotKLast[1]); + + // k16 + for (int j = 0; j < currentState.length; ++j) { + s = K16_01 * yDotK[0][j] + K16_06 * yDotK[5][j] + K16_07 * yDotK[6][j] + + K16_08 * yDotK[7][j] + K16_09 * yDotK[8][j] + K16_10 * yDotK[9][j] + + K16_11 * yDotK[10][j] + K16_12 * yDotK[11][j] + K16_13 * yDotK[12][j] + + K16_14 * yDotKLast[0][j] + K16_15 * yDotKLast[1][j]; + yTmp[j] = currentState[j] + h * s; + } + integrator.computeDerivatives(pT + C16 * h, yTmp, yDotKLast[2]); + + } + + /** {@inheritDoc} */ + @Override + public void writeExternal(final ObjectOutput out) + throws IOException { + + try { + // save the local attributes + finalizeStep(); + } catch (MaxCountExceededException mcee) { + final IOException ioe = new IOException(mcee.getLocalizedMessage()); + ioe.initCause(mcee); + throw ioe; + } + + final int dimension = (currentState == null) ? -1 : currentState.length; + out.writeInt(dimension); + for (int i = 0; i < dimension; ++i) { + out.writeDouble(yDotKLast[0][i]); + out.writeDouble(yDotKLast[1][i]); + out.writeDouble(yDotKLast[2][i]); + } + + // save the state of the base class + super.writeExternal(out); + + } + + /** {@inheritDoc} */ + @Override + public void readExternal(final ObjectInput in) + throws IOException, ClassNotFoundException { + + // read the local attributes + yDotKLast = new double[3][]; + final int dimension = in.readInt(); + yDotKLast[0] = (dimension < 0) ? null : new double[dimension]; + yDotKLast[1] = (dimension < 0) ? null : new double[dimension]; + yDotKLast[2] = (dimension < 0) ? null : new double[dimension]; + + for (int i = 0; i < dimension; ++i) { + yDotKLast[0][i] = in.readDouble(); + yDotKLast[1][i] = in.readDouble(); + yDotKLast[2][i] = in.readDouble(); + } + + // read the base state + super.readExternal(in); + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/EmbeddedRungeKuttaFieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/EmbeddedRungeKuttaFieldIntegrator.java new file mode 100644 index 0000000..036cf01 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/EmbeddedRungeKuttaFieldIntegrator.java @@ -0,0 +1,385 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.Field; +import org.apache.commons.math3.RealFieldElement; +import org.apache.commons.math3.exception.DimensionMismatchException; +import org.apache.commons.math3.exception.MaxCountExceededException; +import org.apache.commons.math3.exception.NoBracketingException; +import org.apache.commons.math3.exception.NumberIsTooSmallException; +import org.apache.commons.math3.ode.FieldEquationsMapper; +import org.apache.commons.math3.ode.FieldExpandableODE; +import org.apache.commons.math3.ode.FieldODEState; +import org.apache.commons.math3.ode.FieldODEStateAndDerivative; +import org.apache.commons.math3.util.MathArrays; +import org.apache.commons.math3.util.MathUtils; + +/** + * This class implements the common part of all embedded Runge-Kutta + * integrators for Ordinary Differential Equations. + * + * <p>These methods are embedded explicit Runge-Kutta methods with two + * sets of coefficients allowing to estimate the error, their Butcher + * arrays are as follows : + * <pre> + * 0 | + * c2 | a21 + * c3 | a31 a32 + * ... | ... + * cs | as1 as2 ... ass-1 + * |-------------------------- + * | b1 b2 ... bs-1 bs + * | b'1 b'2 ... b's-1 b's + * </pre> + * </p> + * + * <p>In fact, we rather use the array defined by ej = bj - b'j to + * compute directly the error rather than computing two estimates and + * then comparing them.</p> + * + * <p>Some methods are qualified as <i>fsal</i> (first same as last) + * methods. This means the last evaluation of the derivatives in one + * step is the same as the first in the next step. Then, this + * evaluation can be reused from one step to the next one and the cost + * of such a method is really s-1 evaluations despite the method still + * has s stages. This behaviour is true only for successful steps, if + * the step is rejected after the error estimation phase, no + * evaluation is saved. For an <i>fsal</i> method, we have cs = 1 and + * asi = bi for all i.</p> + * + * @param <T> the type of the field elements + * @since 3.6 + */ + +public abstract class EmbeddedRungeKuttaFieldIntegrator<T extends RealFieldElement<T>> + extends AdaptiveStepsizeFieldIntegrator<T> + implements FieldButcherArrayProvider<T> { + + /** Index of the pre-computed derivative for <i>fsal</i> methods. */ + private final int fsal; + + /** Time steps from Butcher array (without the first zero). */ + private final T[] c; + + /** Internal weights from Butcher array (without the first empty row). */ + private final T[][] a; + + /** External weights for the high order method from Butcher array. */ + private final T[] b; + + /** Stepsize control exponent. */ + private final T exp; + + /** Safety factor for stepsize control. */ + private T safety; + + /** Minimal reduction factor for stepsize control. */ + private T minReduction; + + /** Maximal growth factor for stepsize control. */ + private T maxGrowth; + + /** Build a Runge-Kutta integrator with the given Butcher array. + * @param field field to which the time and state vector elements belong + * @param name name of the method + * @param fsal index of the pre-computed derivative for <i>fsal</i> methods + * or -1 if method is not <i>fsal</i> + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + */ + protected EmbeddedRungeKuttaFieldIntegrator(final Field<T> field, final String name, final int fsal, + final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) { + + super(field, name, minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance); + + this.fsal = fsal; + this.c = getC(); + this.a = getA(); + this.b = getB(); + + exp = field.getOne().divide(-getOrder()); + + // set the default values of the algorithm control parameters + setSafety(field.getZero().add(0.9)); + setMinReduction(field.getZero().add(0.2)); + setMaxGrowth(field.getZero().add(10.0)); + + } + + /** Build a Runge-Kutta integrator with the given Butcher array. + * @param field field to which the time and state vector elements belong + * @param name name of the method + * @param fsal index of the pre-computed derivative for <i>fsal</i> methods + * or -1 if method is not <i>fsal</i> + * @param minStep minimal step (must be positive even for backward + * integration), the last step can be smaller than this + * @param maxStep maximal step (must be positive even for backward + * integration) + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + */ + protected EmbeddedRungeKuttaFieldIntegrator(final Field<T> field, final String name, final int fsal, + final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) { + + super(field, name, minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance); + + this.fsal = fsal; + this.c = getC(); + this.a = getA(); + this.b = getB(); + + exp = field.getOne().divide(-getOrder()); + + // set the default values of the algorithm control parameters + setSafety(field.getZero().add(0.9)); + setMinReduction(field.getZero().add(0.2)); + setMaxGrowth(field.getZero().add(10.0)); + + } + + /** Create a fraction. + * @param p numerator + * @param q denominator + * @return p/q computed in the instance field + */ + protected T fraction(final int p, final int q) { + return getField().getOne().multiply(p).divide(q); + } + + /** Create a fraction. + * @param p numerator + * @param q denominator + * @return p/q computed in the instance field + */ + protected T fraction(final double p, final double q) { + return getField().getOne().multiply(p).divide(q); + } + + /** Create an interpolator. + * @param forward integration direction indicator + * @param yDotK slopes at the intermediate points + * @param globalPreviousState start of the global step + * @param globalCurrentState end of the global step + * @param mapper equations mapper for the all equations + * @return external weights for the high order method from Butcher array + */ + protected abstract RungeKuttaFieldStepInterpolator<T> createInterpolator(boolean forward, T[][] yDotK, + final FieldODEStateAndDerivative<T> globalPreviousState, + final FieldODEStateAndDerivative<T> globalCurrentState, + FieldEquationsMapper<T> mapper); + /** Get the order of the method. + * @return order of the method + */ + public abstract int getOrder(); + + /** Get the safety factor for stepsize control. + * @return safety factor + */ + public T getSafety() { + return safety; + } + + /** Set the safety factor for stepsize control. + * @param safety safety factor + */ + public void setSafety(final T safety) { + this.safety = safety; + } + + /** {@inheritDoc} */ + public FieldODEStateAndDerivative<T> integrate(final FieldExpandableODE<T> equations, + final FieldODEState<T> initialState, final T finalTime) + throws NumberIsTooSmallException, DimensionMismatchException, + MaxCountExceededException, NoBracketingException { + + sanityChecks(initialState, finalTime); + final T t0 = initialState.getTime(); + final T[] y0 = equations.getMapper().mapState(initialState); + setStepStart(initIntegration(equations, t0, y0, finalTime)); + final boolean forward = finalTime.subtract(initialState.getTime()).getReal() > 0; + + // create some internal working arrays + final int stages = c.length + 1; + T[] y = y0; + final T[][] yDotK = MathArrays.buildArray(getField(), stages, -1); + final T[] yTmp = MathArrays.buildArray(getField(), y0.length); + + // set up integration control objects + T hNew = getField().getZero(); + boolean firstTime = true; + + // main integration loop + setIsLastStep(false); + do { + + // iterate over step size, ensuring local normalized error is smaller than 1 + T error = getField().getZero().add(10); + while (error.subtract(1.0).getReal() >= 0) { + + // first stage + y = equations.getMapper().mapState(getStepStart()); + yDotK[0] = equations.getMapper().mapDerivative(getStepStart()); + + if (firstTime) { + final T[] scale = MathArrays.buildArray(getField(), mainSetDimension); + if (vecAbsoluteTolerance == null) { + for (int i = 0; i < scale.length; ++i) { + scale[i] = y[i].abs().multiply(scalRelativeTolerance).add(scalAbsoluteTolerance); + } + } else { + for (int i = 0; i < scale.length; ++i) { + scale[i] = y[i].abs().multiply(vecRelativeTolerance[i]).add(vecAbsoluteTolerance[i]); + } + } + hNew = initializeStep(forward, getOrder(), scale, getStepStart(), equations.getMapper()); + firstTime = false; + } + + setStepSize(hNew); + if (forward) { + if (getStepStart().getTime().add(getStepSize()).subtract(finalTime).getReal() >= 0) { + setStepSize(finalTime.subtract(getStepStart().getTime())); + } + } else { + if (getStepStart().getTime().add(getStepSize()).subtract(finalTime).getReal() <= 0) { + setStepSize(finalTime.subtract(getStepStart().getTime())); + } + } + + // next stages + for (int k = 1; k < stages; ++k) { + + for (int j = 0; j < y0.length; ++j) { + T sum = yDotK[0][j].multiply(a[k-1][0]); + for (int l = 1; l < k; ++l) { + sum = sum.add(yDotK[l][j].multiply(a[k-1][l])); + } + yTmp[j] = y[j].add(getStepSize().multiply(sum)); + } + + yDotK[k] = computeDerivatives(getStepStart().getTime().add(getStepSize().multiply(c[k-1])), yTmp); + + } + + // estimate the state at the end of the step + for (int j = 0; j < y0.length; ++j) { + T sum = yDotK[0][j].multiply(b[0]); + for (int l = 1; l < stages; ++l) { + sum = sum.add(yDotK[l][j].multiply(b[l])); + } + yTmp[j] = y[j].add(getStepSize().multiply(sum)); + } + + // estimate the error at the end of the step + error = estimateError(yDotK, y, yTmp, getStepSize()); + if (error.subtract(1.0).getReal() >= 0) { + // reject the step and attempt to reduce error by stepsize control + final T factor = MathUtils.min(maxGrowth, + MathUtils.max(minReduction, safety.multiply(error.pow(exp)))); + hNew = filterStep(getStepSize().multiply(factor), forward, false); + } + + } + final T stepEnd = getStepStart().getTime().add(getStepSize()); + final T[] yDotTmp = (fsal >= 0) ? yDotK[fsal] : computeDerivatives(stepEnd, yTmp); + final FieldODEStateAndDerivative<T> stateTmp = new FieldODEStateAndDerivative<T>(stepEnd, yTmp, yDotTmp); + + // local error is small enough: accept the step, trigger events and step handlers + System.arraycopy(yTmp, 0, y, 0, y0.length); + setStepStart(acceptStep(createInterpolator(forward, yDotK, getStepStart(), stateTmp, equations.getMapper()), + finalTime)); + + if (!isLastStep()) { + + // stepsize control for next step + final T factor = MathUtils.min(maxGrowth, + MathUtils.max(minReduction, safety.multiply(error.pow(exp)))); + final T scaledH = getStepSize().multiply(factor); + final T nextT = getStepStart().getTime().add(scaledH); + final boolean nextIsLast = forward ? + nextT.subtract(finalTime).getReal() >= 0 : + nextT.subtract(finalTime).getReal() <= 0; + hNew = filterStep(scaledH, forward, nextIsLast); + + final T filteredNextT = getStepStart().getTime().add(hNew); + final boolean filteredNextIsLast = forward ? + filteredNextT.subtract(finalTime).getReal() >= 0 : + filteredNextT.subtract(finalTime).getReal() <= 0; + if (filteredNextIsLast) { + hNew = finalTime.subtract(getStepStart().getTime()); + } + + } + + } while (!isLastStep()); + + final FieldODEStateAndDerivative<T> finalState = getStepStart(); + resetInternalState(); + return finalState; + + } + + /** Get the minimal reduction factor for stepsize control. + * @return minimal reduction factor + */ + public T getMinReduction() { + return minReduction; + } + + /** Set the minimal reduction factor for stepsize control. + * @param minReduction minimal reduction factor + */ + public void setMinReduction(final T minReduction) { + this.minReduction = minReduction; + } + + /** Get the maximal growth factor for stepsize control. + * @return maximal growth factor + */ + public T getMaxGrowth() { + return maxGrowth; + } + + /** Set the maximal growth factor for stepsize control. + * @param maxGrowth maximal growth factor + */ + public void setMaxGrowth(final T maxGrowth) { + this.maxGrowth = maxGrowth; + } + + /** Compute the error ratio. + * @param yDotK derivatives computed during the first stages + * @param y0 estimate of the step at the start of the step + * @param y1 estimate of the step at the end of the step + * @param h current step + * @return error ratio, greater than 1 if step should be rejected + */ + protected abstract T estimateError(T[][] yDotK, T[] y0, T[] y1, T h); + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/EmbeddedRungeKuttaIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/EmbeddedRungeKuttaIntegrator.java new file mode 100644 index 0000000..098d2e5 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/EmbeddedRungeKuttaIntegrator.java @@ -0,0 +1,380 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.exception.DimensionMismatchException; +import org.apache.commons.math3.exception.MaxCountExceededException; +import org.apache.commons.math3.exception.NoBracketingException; +import org.apache.commons.math3.exception.NumberIsTooSmallException; +import org.apache.commons.math3.ode.ExpandableStatefulODE; +import org.apache.commons.math3.util.FastMath; + +/** + * This class implements the common part of all embedded Runge-Kutta + * integrators for Ordinary Differential Equations. + * + * <p>These methods are embedded explicit Runge-Kutta methods with two + * sets of coefficients allowing to estimate the error, their Butcher + * arrays are as follows : + * <pre> + * 0 | + * c2 | a21 + * c3 | a31 a32 + * ... | ... + * cs | as1 as2 ... ass-1 + * |-------------------------- + * | b1 b2 ... bs-1 bs + * | b'1 b'2 ... b's-1 b's + * </pre> + * </p> + * + * <p>In fact, we rather use the array defined by ej = bj - b'j to + * compute directly the error rather than computing two estimates and + * then comparing them.</p> + * + * <p>Some methods are qualified as <i>fsal</i> (first same as last) + * methods. This means the last evaluation of the derivatives in one + * step is the same as the first in the next step. Then, this + * evaluation can be reused from one step to the next one and the cost + * of such a method is really s-1 evaluations despite the method still + * has s stages. This behaviour is true only for successful steps, if + * the step is rejected after the error estimation phase, no + * evaluation is saved. For an <i>fsal</i> method, we have cs = 1 and + * asi = bi for all i.</p> + * + * @since 1.2 + */ + +public abstract class EmbeddedRungeKuttaIntegrator + extends AdaptiveStepsizeIntegrator { + + /** Indicator for <i>fsal</i> methods. */ + private final boolean fsal; + + /** Time steps from Butcher array (without the first zero). */ + private final double[] c; + + /** Internal weights from Butcher array (without the first empty row). */ + private final double[][] a; + + /** External weights for the high order method from Butcher array. */ + private final double[] b; + + /** Prototype of the step interpolator. */ + private final RungeKuttaStepInterpolator prototype; + + /** Stepsize control exponent. */ + private final double exp; + + /** Safety factor for stepsize control. */ + private double safety; + + /** Minimal reduction factor for stepsize control. */ + private double minReduction; + + /** Maximal growth factor for stepsize control. */ + private double maxGrowth; + + /** Build a Runge-Kutta integrator with the given Butcher array. + * @param name name of the method + * @param fsal indicate that the method is an <i>fsal</i> + * @param c time steps from Butcher array (without the first zero) + * @param a internal weights from Butcher array (without the first empty row) + * @param b propagation weights for the high order method from Butcher array + * @param prototype prototype of the step interpolator to use + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + */ + protected EmbeddedRungeKuttaIntegrator(final String name, final boolean fsal, + final double[] c, final double[][] a, final double[] b, + final RungeKuttaStepInterpolator prototype, + final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) { + + super(name, minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance); + + this.fsal = fsal; + this.c = c; + this.a = a; + this.b = b; + this.prototype = prototype; + + exp = -1.0 / getOrder(); + + // set the default values of the algorithm control parameters + setSafety(0.9); + setMinReduction(0.2); + setMaxGrowth(10.0); + + } + + /** Build a Runge-Kutta integrator with the given Butcher array. + * @param name name of the method + * @param fsal indicate that the method is an <i>fsal</i> + * @param c time steps from Butcher array (without the first zero) + * @param a internal weights from Butcher array (without the first empty row) + * @param b propagation weights for the high order method from Butcher array + * @param prototype prototype of the step interpolator to use + * @param minStep minimal step (must be positive even for backward + * integration), the last step can be smaller than this + * @param maxStep maximal step (must be positive even for backward + * integration) + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + */ + protected EmbeddedRungeKuttaIntegrator(final String name, final boolean fsal, + final double[] c, final double[][] a, final double[] b, + final RungeKuttaStepInterpolator prototype, + final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) { + + super(name, minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance); + + this.fsal = fsal; + this.c = c; + this.a = a; + this.b = b; + this.prototype = prototype; + + exp = -1.0 / getOrder(); + + // set the default values of the algorithm control parameters + setSafety(0.9); + setMinReduction(0.2); + setMaxGrowth(10.0); + + } + + /** Get the order of the method. + * @return order of the method + */ + public abstract int getOrder(); + + /** Get the safety factor for stepsize control. + * @return safety factor + */ + public double getSafety() { + return safety; + } + + /** Set the safety factor for stepsize control. + * @param safety safety factor + */ + public void setSafety(final double safety) { + this.safety = safety; + } + + /** {@inheritDoc} */ + @Override + public void integrate(final ExpandableStatefulODE equations, final double t) + throws NumberIsTooSmallException, DimensionMismatchException, + MaxCountExceededException, NoBracketingException { + + sanityChecks(equations, t); + setEquations(equations); + final boolean forward = t > equations.getTime(); + + // create some internal working arrays + final double[] y0 = equations.getCompleteState(); + final double[] y = y0.clone(); + final int stages = c.length + 1; + final double[][] yDotK = new double[stages][y.length]; + final double[] yTmp = y0.clone(); + final double[] yDotTmp = new double[y.length]; + + // set up an interpolator sharing the integrator arrays + final RungeKuttaStepInterpolator interpolator = (RungeKuttaStepInterpolator) prototype.copy(); + interpolator.reinitialize(this, yTmp, yDotK, forward, + equations.getPrimaryMapper(), equations.getSecondaryMappers()); + interpolator.storeTime(equations.getTime()); + + // set up integration control objects + stepStart = equations.getTime(); + double hNew = 0; + boolean firstTime = true; + initIntegration(equations.getTime(), y0, t); + + // main integration loop + isLastStep = false; + do { + + interpolator.shift(); + + // iterate over step size, ensuring local normalized error is smaller than 1 + double error = 10; + while (error >= 1.0) { + + if (firstTime || !fsal) { + // first stage + computeDerivatives(stepStart, y, yDotK[0]); + } + + if (firstTime) { + final double[] scale = new double[mainSetDimension]; + if (vecAbsoluteTolerance == null) { + for (int i = 0; i < scale.length; ++i) { + scale[i] = scalAbsoluteTolerance + scalRelativeTolerance * FastMath.abs(y[i]); + } + } else { + for (int i = 0; i < scale.length; ++i) { + scale[i] = vecAbsoluteTolerance[i] + vecRelativeTolerance[i] * FastMath.abs(y[i]); + } + } + hNew = initializeStep(forward, getOrder(), scale, + stepStart, y, yDotK[0], yTmp, yDotK[1]); + firstTime = false; + } + + stepSize = hNew; + if (forward) { + if (stepStart + stepSize >= t) { + stepSize = t - stepStart; + } + } else { + if (stepStart + stepSize <= t) { + stepSize = t - stepStart; + } + } + + // next stages + for (int k = 1; k < stages; ++k) { + + for (int j = 0; j < y0.length; ++j) { + double sum = a[k-1][0] * yDotK[0][j]; + for (int l = 1; l < k; ++l) { + sum += a[k-1][l] * yDotK[l][j]; + } + yTmp[j] = y[j] + stepSize * sum; + } + + computeDerivatives(stepStart + c[k-1] * stepSize, yTmp, yDotK[k]); + + } + + // estimate the state at the end of the step + for (int j = 0; j < y0.length; ++j) { + double sum = b[0] * yDotK[0][j]; + for (int l = 1; l < stages; ++l) { + sum += b[l] * yDotK[l][j]; + } + yTmp[j] = y[j] + stepSize * sum; + } + + // estimate the error at the end of the step + error = estimateError(yDotK, y, yTmp, stepSize); + if (error >= 1.0) { + // reject the step and attempt to reduce error by stepsize control + final double factor = + FastMath.min(maxGrowth, + FastMath.max(minReduction, safety * FastMath.pow(error, exp))); + hNew = filterStep(stepSize * factor, forward, false); + } + + } + + // local error is small enough: accept the step, trigger events and step handlers + interpolator.storeTime(stepStart + stepSize); + System.arraycopy(yTmp, 0, y, 0, y0.length); + System.arraycopy(yDotK[stages - 1], 0, yDotTmp, 0, y0.length); + stepStart = acceptStep(interpolator, y, yDotTmp, t); + System.arraycopy(y, 0, yTmp, 0, y.length); + + if (!isLastStep) { + + // prepare next step + interpolator.storeTime(stepStart); + + if (fsal) { + // save the last evaluation for the next step + System.arraycopy(yDotTmp, 0, yDotK[0], 0, y0.length); + } + + // stepsize control for next step + final double factor = + FastMath.min(maxGrowth, FastMath.max(minReduction, safety * FastMath.pow(error, exp))); + final double scaledH = stepSize * factor; + final double nextT = stepStart + scaledH; + final boolean nextIsLast = forward ? (nextT >= t) : (nextT <= t); + hNew = filterStep(scaledH, forward, nextIsLast); + + final double filteredNextT = stepStart + hNew; + final boolean filteredNextIsLast = forward ? (filteredNextT >= t) : (filteredNextT <= t); + if (filteredNextIsLast) { + hNew = t - stepStart; + } + + } + + } while (!isLastStep); + + // dispatch results + equations.setTime(stepStart); + equations.setCompleteState(y); + + resetInternalState(); + + } + + /** Get the minimal reduction factor for stepsize control. + * @return minimal reduction factor + */ + public double getMinReduction() { + return minReduction; + } + + /** Set the minimal reduction factor for stepsize control. + * @param minReduction minimal reduction factor + */ + public void setMinReduction(final double minReduction) { + this.minReduction = minReduction; + } + + /** Get the maximal growth factor for stepsize control. + * @return maximal growth factor + */ + public double getMaxGrowth() { + return maxGrowth; + } + + /** Set the maximal growth factor for stepsize control. + * @param maxGrowth maximal growth factor + */ + public void setMaxGrowth(final double maxGrowth) { + this.maxGrowth = maxGrowth; + } + + /** Compute the error ratio. + * @param yDotK derivatives computed during the first stages + * @param y0 estimate of the step at the start of the step + * @param y1 estimate of the step at the end of the step + * @param h current step + * @return error ratio, greater than 1 if step should be rejected + */ + protected abstract double estimateError(double[][] yDotK, + double[] y0, double[] y1, + double h); + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/EulerFieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/EulerFieldIntegrator.java new file mode 100644 index 0000000..38516cf --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/EulerFieldIntegrator.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.Field; +import org.apache.commons.math3.RealFieldElement; +import org.apache.commons.math3.ode.FieldEquationsMapper; +import org.apache.commons.math3.ode.FieldODEStateAndDerivative; +import org.apache.commons.math3.util.MathArrays; + +/** + * This class implements a simple Euler integrator for Ordinary + * Differential Equations. + * + * <p>The Euler algorithm is the simplest one that can be used to + * integrate ordinary differential equations. It is a simple inversion + * of the forward difference expression : + * <code>f'=(f(t+h)-f(t))/h</code> which leads to + * <code>f(t+h)=f(t)+hf'</code>. The interpolation scheme used for + * dense output is the linear scheme already used for integration.</p> + * + * <p>This algorithm looks cheap because it needs only one function + * evaluation per step. However, as it uses linear estimates, it needs + * very small steps to achieve high accuracy, and small steps lead to + * numerical errors and instabilities.</p> + * + * <p>This algorithm is almost never used and has been included in + * this package only as a comparison reference for more useful + * integrators.</p> + * + * @see MidpointFieldIntegrator + * @see ClassicalRungeKuttaFieldIntegrator + * @see GillFieldIntegrator + * @see ThreeEighthesFieldIntegrator + * @see LutherFieldIntegrator + * @param <T> the type of the field elements + * @since 3.6 + */ + +public class EulerFieldIntegrator<T extends RealFieldElement<T>> extends RungeKuttaFieldIntegrator<T> { + + /** Simple constructor. + * Build an Euler integrator with the given step. + * @param field field to which the time and state vector elements belong + * @param step integration step + */ + public EulerFieldIntegrator(final Field<T> field, final T step) { + super(field, "Euler", step); + } + + /** {@inheritDoc} */ + public T[] getC() { + return MathArrays.buildArray(getField(), 0); + } + + /** {@inheritDoc} */ + public T[][] getA() { + return MathArrays.buildArray(getField(), 0, 0); + } + + /** {@inheritDoc} */ + public T[] getB() { + final T[] b = MathArrays.buildArray(getField(), 1); + b[0] = getField().getOne(); + return b; + } + + /** {@inheritDoc} */ + @Override + protected EulerFieldStepInterpolator<T> + createInterpolator(final boolean forward, T[][] yDotK, + final FieldODEStateAndDerivative<T> globalPreviousState, + final FieldODEStateAndDerivative<T> globalCurrentState, + final FieldEquationsMapper<T> mapper) { + return new EulerFieldStepInterpolator<T>(getField(), forward, yDotK, + globalPreviousState, globalCurrentState, + globalPreviousState, globalCurrentState, + mapper); + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/EulerFieldStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/EulerFieldStepInterpolator.java new file mode 100644 index 0000000..0907b8e --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/EulerFieldStepInterpolator.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.Field; +import org.apache.commons.math3.RealFieldElement; +import org.apache.commons.math3.ode.FieldEquationsMapper; +import org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** + * This class implements a linear interpolator for step. + * + * <p>This interpolator computes dense output inside the last + * step computed. The interpolation equation is consistent with the + * integration scheme : + * <ul> + * <li>Using reference point at step start:<br> + * y(t<sub>n</sub> + θ h) = y (t<sub>n</sub>) + θ h y' + * </li> + * <li>Using reference point at step end:<br> + * y(t<sub>n</sub> + θ h) = y (t<sub>n</sub> + h) - (1-θ) h y' + * </li> + * </ul> + * </p> + * + * where θ belongs to [0 ; 1] and where y' is the evaluation of + * the derivatives already computed during the step.</p> + * + * @see EulerFieldIntegrator + * @param <T> the type of the field elements + * @since 3.6 + */ + +class EulerFieldStepInterpolator<T extends RealFieldElement<T>> + extends RungeKuttaFieldStepInterpolator<T> { + + /** Simple constructor. + * @param field field to which the time and state vector elements belong + * @param forward integration direction indicator + * @param yDotK slopes at the intermediate points + * @param globalPreviousState start of the global step + * @param globalCurrentState end of the global step + * @param softPreviousState start of the restricted step + * @param softCurrentState end of the restricted step + * @param mapper equations mapper for the all equations + */ + EulerFieldStepInterpolator(final Field<T> field, final boolean forward, + final T[][] yDotK, + final FieldODEStateAndDerivative<T> globalPreviousState, + final FieldODEStateAndDerivative<T> globalCurrentState, + final FieldODEStateAndDerivative<T> softPreviousState, + final FieldODEStateAndDerivative<T> softCurrentState, + final FieldEquationsMapper<T> mapper) { + super(field, forward, yDotK, + globalPreviousState, globalCurrentState, softPreviousState, softCurrentState, + mapper); + } + + /** {@inheritDoc} */ + @Override + protected EulerFieldStepInterpolator<T> create(final Field<T> newField, final boolean newForward, final T[][] newYDotK, + final FieldODEStateAndDerivative<T> newGlobalPreviousState, + final FieldODEStateAndDerivative<T> newGlobalCurrentState, + final FieldODEStateAndDerivative<T> newSoftPreviousState, + final FieldODEStateAndDerivative<T> newSoftCurrentState, + final FieldEquationsMapper<T> newMapper) { + return new EulerFieldStepInterpolator<T>(newField, newForward, newYDotK, + newGlobalPreviousState, newGlobalCurrentState, + newSoftPreviousState, newSoftCurrentState, + newMapper); + } + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override + protected FieldODEStateAndDerivative<T> computeInterpolatedStateAndDerivatives(final FieldEquationsMapper<T> mapper, + final T time, final T theta, + final T thetaH, final T oneMinusThetaH) { + final T[] interpolatedState; + final T[] interpolatedDerivatives; + if (getGlobalPreviousState() != null && theta.getReal() <= 0.5) { + interpolatedState = previousStateLinearCombination(thetaH); + interpolatedDerivatives = derivativeLinearCombination(time.getField().getOne()); + } else { + interpolatedState = currentStateLinearCombination(oneMinusThetaH.negate()); + interpolatedDerivatives = derivativeLinearCombination(time.getField().getOne()); + } + + return new FieldODEStateAndDerivative<T>(time, interpolatedState, interpolatedDerivatives); + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/EulerIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/EulerIntegrator.java new file mode 100644 index 0000000..22c15c5 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/EulerIntegrator.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + + +/** + * This class implements a simple Euler integrator for Ordinary + * Differential Equations. + * + * <p>The Euler algorithm is the simplest one that can be used to + * integrate ordinary differential equations. It is a simple inversion + * of the forward difference expression : + * <code>f'=(f(t+h)-f(t))/h</code> which leads to + * <code>f(t+h)=f(t)+hf'</code>. The interpolation scheme used for + * dense output is the linear scheme already used for integration.</p> + * + * <p>This algorithm looks cheap because it needs only one function + * evaluation per step. However, as it uses linear estimates, it needs + * very small steps to achieve high accuracy, and small steps lead to + * numerical errors and instabilities.</p> + * + * <p>This algorithm is almost never used and has been included in + * this package only as a comparison reference for more useful + * integrators.</p> + * + * @see MidpointIntegrator + * @see ClassicalRungeKuttaIntegrator + * @see GillIntegrator + * @see ThreeEighthesIntegrator + * @see LutherIntegrator + * @since 1.2 + */ + +public class EulerIntegrator extends RungeKuttaIntegrator { + + /** Time steps Butcher array. */ + private static final double[] STATIC_C = { + }; + + /** Internal weights Butcher array. */ + private static final double[][] STATIC_A = { + }; + + /** Propagation weights Butcher array. */ + private static final double[] STATIC_B = { + 1.0 + }; + + /** Simple constructor. + * Build an Euler integrator with the given step. + * @param step integration step + */ + public EulerIntegrator(final double step) { + super("Euler", STATIC_C, STATIC_A, STATIC_B, new EulerStepInterpolator(), step); + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/EulerStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/EulerStepInterpolator.java new file mode 100644 index 0000000..331cb14 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/EulerStepInterpolator.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.ode.sampling.StepInterpolator; + +/** + * This class implements a linear interpolator for step. + * + * <p>This interpolator computes dense output inside the last + * step computed. The interpolation equation is consistent with the + * integration scheme : + * <ul> + * <li>Using reference point at step start:<br> + * y(t<sub>n</sub> + θ h) = y (t<sub>n</sub>) + θ h y' + * </li> + * <li>Using reference point at step end:<br> + * y(t<sub>n</sub> + θ h) = y (t<sub>n</sub> + h) - (1-θ) h y' + * </li> + * </ul> + * </p> + * + * where θ belongs to [0 ; 1] and where y' is the evaluation of + * the derivatives already computed during the step.</p> + * + * @see EulerIntegrator + * @since 1.2 + */ + +class EulerStepInterpolator + extends RungeKuttaStepInterpolator { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 20111120L; + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link + * org.apache.commons.math3.ode.sampling.AbstractStepInterpolator#reinitialize} + * method should be called before using the instance in order to + * initialize the internal arrays. This constructor is used only + * in order to delay the initialization in some cases. The {@link + * RungeKuttaIntegrator} class uses the prototyping design pattern + * to create the step interpolators by cloning an uninitialized model + * and later initializing the copy. + */ + // CHECKSTYLE: stop RedundantModifier + // the public modifier here is needed for serialization + public EulerStepInterpolator() { + } + // CHECKSTYLE: resume RedundantModifier + + /** Copy constructor. + * @param interpolator interpolator to copy from. The copy is a deep + * copy: its arrays are separated from the original arrays of the + * instance + */ + EulerStepInterpolator(final EulerStepInterpolator interpolator) { + super(interpolator); + } + + /** {@inheritDoc} */ + @Override + protected StepInterpolator doCopy() { + return new EulerStepInterpolator(this); + } + + + /** {@inheritDoc} */ + @Override + protected void computeInterpolatedStateAndDerivatives(final double theta, + final double oneMinusThetaH) { + if ((previousState != null) && (theta <= 0.5)) { + for (int i = 0; i < interpolatedState.length; ++i) { + interpolatedState[i] = previousState[i] + theta * h * yDotK[0][i]; + } + System.arraycopy(yDotK[0], 0, interpolatedDerivatives, 0, interpolatedDerivatives.length); + } else { + for (int i = 0; i < interpolatedState.length; ++i) { + interpolatedState[i] = currentState[i] - oneMinusThetaH * yDotK[0][i]; + } + System.arraycopy(yDotK[0], 0, interpolatedDerivatives, 0, interpolatedDerivatives.length); + } + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/FieldButcherArrayProvider.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/FieldButcherArrayProvider.java new file mode 100644 index 0000000..b37d4cb --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/FieldButcherArrayProvider.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.RealFieldElement; + +/** This interface represents an integrator based on Butcher arrays. + * @see RungeKuttaFieldIntegrator + * @see EmbeddedRungeKuttaFieldIntegrator + * @param <T> the type of the field elements + * @since 3.6 + */ + +public interface FieldButcherArrayProvider<T extends RealFieldElement<T>> { + + /** Get the time steps from Butcher array (without the first zero). + * @return time steps from Butcher array (without the first zero + */ + T[] getC(); + + /** Get the internal weights from Butcher array (without the first empty row). + * @return internal weights from Butcher array (without the first empty row) + */ + T[][] getA(); + + /** Get the external weights for the high order method from Butcher array. + * @return external weights for the high order method from Butcher array + */ + T[] getB(); + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/GillFieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/GillFieldIntegrator.java new file mode 100644 index 0000000..d5f7c64 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/GillFieldIntegrator.java @@ -0,0 +1,121 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.Field; +import org.apache.commons.math3.RealFieldElement; +import org.apache.commons.math3.ode.FieldEquationsMapper; +import org.apache.commons.math3.ode.FieldODEStateAndDerivative; +import org.apache.commons.math3.util.MathArrays; + + +/** + * This class implements the Gill fourth order Runge-Kutta + * integrator for Ordinary Differential Equations . + + * <p>This method is an explicit Runge-Kutta method, its Butcher-array + * is the following one : + * <pre> + * 0 | 0 0 0 0 + * 1/2 | 1/2 0 0 0 + * 1/2 | (q-1)/2 (2-q)/2 0 0 + * 1 | 0 -q/2 (2+q)/2 0 + * |------------------------------- + * | 1/6 (2-q)/6 (2+q)/6 1/6 + * </pre> + * where q = sqrt(2)</p> + * + * @see EulerFieldIntegrator + * @see ClassicalRungeKuttaFieldIntegrator + * @see MidpointFieldIntegrator + * @see ThreeEighthesFieldIntegrator + * @see LutherFieldIntegrator + * @param <T> the type of the field elements + * @since 3.6 + */ + +public class GillFieldIntegrator<T extends RealFieldElement<T>> + extends RungeKuttaFieldIntegrator<T> { + + /** Simple constructor. + * Build a fourth-order Gill integrator with the given step. + * @param field field to which the time and state vector elements belong + * @param step integration step + */ + public GillFieldIntegrator(final Field<T> field, final T step) { + super(field, "Gill", step); + } + + /** {@inheritDoc} */ + public T[] getC() { + final T[] c = MathArrays.buildArray(getField(), 3); + c[0] = fraction(1, 2); + c[1] = c[0]; + c[2] = getField().getOne(); + return c; + } + + /** {@inheritDoc} */ + public T[][] getA() { + + final T two = getField().getZero().add(2); + final T sqrtTwo = two.sqrt(); + + final T[][] a = MathArrays.buildArray(getField(), 3, -1); + for (int i = 0; i < a.length; ++i) { + a[i] = MathArrays.buildArray(getField(), i + 1); + } + a[0][0] = fraction(1, 2); + a[1][0] = sqrtTwo.subtract(1).multiply(0.5); + a[1][1] = sqrtTwo.subtract(2).multiply(-0.5); + a[2][0] = getField().getZero(); + a[2][1] = sqrtTwo.multiply(-0.5); + a[2][2] = sqrtTwo.add(2).multiply(0.5); + return a; + } + + /** {@inheritDoc} */ + public T[] getB() { + + final T two = getField().getZero().add(2); + final T sqrtTwo = two.sqrt(); + + final T[] b = MathArrays.buildArray(getField(), 4); + b[0] = fraction(1, 6); + b[1] = sqrtTwo.subtract(2).divide(-6); + b[2] = sqrtTwo.add(2).divide(6); + b[3] = b[0]; + + return b; + + } + + /** {@inheritDoc} */ + @Override + protected GillFieldStepInterpolator<T> + createInterpolator(final boolean forward, T[][] yDotK, + final FieldODEStateAndDerivative<T> globalPreviousState, + final FieldODEStateAndDerivative<T> globalCurrentState, + final FieldEquationsMapper<T> mapper) { + return new GillFieldStepInterpolator<T>(getField(), forward, yDotK, + globalPreviousState, globalCurrentState, + globalPreviousState, globalCurrentState, + mapper); + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/GillFieldStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/GillFieldStepInterpolator.java new file mode 100644 index 0000000..5639ed5 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/GillFieldStepInterpolator.java @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.Field; +import org.apache.commons.math3.RealFieldElement; +import org.apache.commons.math3.ode.FieldEquationsMapper; +import org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** + * This class implements a step interpolator for the Gill fourth + * order Runge-Kutta integrator. + * + * <p>This interpolator allows to compute dense output inside the last + * step computed. The interpolation equation is consistent with the + * integration scheme : + * <ul> + * <li>Using reference point at step start:<br> + * y(t<sub>n</sub> + θ h) = y (t<sub>n</sub>) + * + θ (h/6) [ (6 - 9 θ + 4 θ<sup>2</sup>) y'<sub>1</sub> + * + ( 6 θ - 4 θ<sup>2</sup>) ((1-1/√2) y'<sub>2</sub> + (1+1/√2)) y'<sub>3</sub>) + * + ( - 3 θ + 4 θ<sup>2</sup>) y'<sub>4</sub> + * ] + * </li> + * <li>Using reference point at step start:<br> + * y(t<sub>n</sub> + θ h) = y (t<sub>n</sub> + h) + * - (1 - θ) (h/6) [ (1 - 5 θ + 4 θ<sup>2</sup>) y'<sub>1</sub> + * + (2 + 2 θ - 4 θ<sup>2</sup>) ((1-1/√2) y'<sub>2</sub> + (1+1/√2)) y'<sub>3</sub>) + * + (1 + θ + 4 θ<sup>2</sup>) y'<sub>4</sub> + * ] + * </li> + * </ul> + * </p> + * where θ belongs to [0 ; 1] and where y'<sub>1</sub> to y'<sub>4</sub> + * are the four evaluations of the derivatives already computed during + * the step.</p> + * + * @see GillFieldIntegrator + * @param <T> the type of the field elements + * @since 3.6 + */ + +class GillFieldStepInterpolator<T extends RealFieldElement<T>> + extends RungeKuttaFieldStepInterpolator<T> { + + /** First Gill coefficient. */ + private final T one_minus_inv_sqrt_2; + + /** Second Gill coefficient. */ + private final T one_plus_inv_sqrt_2; + + /** Simple constructor. + * @param field field to which the time and state vector elements belong + * @param forward integration direction indicator + * @param yDotK slopes at the intermediate points + * @param globalPreviousState start of the global step + * @param globalCurrentState end of the global step + * @param softPreviousState start of the restricted step + * @param softCurrentState end of the restricted step + * @param mapper equations mapper for the all equations + */ + GillFieldStepInterpolator(final Field<T> field, final boolean forward, + final T[][] yDotK, + final FieldODEStateAndDerivative<T> globalPreviousState, + final FieldODEStateAndDerivative<T> globalCurrentState, + final FieldODEStateAndDerivative<T> softPreviousState, + final FieldODEStateAndDerivative<T> softCurrentState, + final FieldEquationsMapper<T> mapper) { + super(field, forward, yDotK, + globalPreviousState, globalCurrentState, softPreviousState, softCurrentState, + mapper); + final T sqrt = field.getZero().add(0.5).sqrt(); + one_minus_inv_sqrt_2 = field.getOne().subtract(sqrt); + one_plus_inv_sqrt_2 = field.getOne().add(sqrt); + } + + /** {@inheritDoc} */ + @Override + protected GillFieldStepInterpolator<T> create(final Field<T> newField, final boolean newForward, final T[][] newYDotK, + final FieldODEStateAndDerivative<T> newGlobalPreviousState, + final FieldODEStateAndDerivative<T> newGlobalCurrentState, + final FieldODEStateAndDerivative<T> newSoftPreviousState, + final FieldODEStateAndDerivative<T> newSoftCurrentState, + final FieldEquationsMapper<T> newMapper) { + return new GillFieldStepInterpolator<T>(newField, newForward, newYDotK, + newGlobalPreviousState, newGlobalCurrentState, + newSoftPreviousState, newSoftCurrentState, + newMapper); + } + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override + protected FieldODEStateAndDerivative<T> computeInterpolatedStateAndDerivatives(final FieldEquationsMapper<T> mapper, + final T time, final T theta, + final T thetaH, final T oneMinusThetaH) { + + final T one = time.getField().getOne(); + final T twoTheta = theta.multiply(2); + final T fourTheta2 = twoTheta.multiply(twoTheta); + final T coeffDot1 = theta.multiply(twoTheta.subtract(3)).add(1); + final T cDot23 = twoTheta.multiply(one.subtract(theta)); + final T coeffDot2 = cDot23.multiply(one_minus_inv_sqrt_2); + final T coeffDot3 = cDot23.multiply(one_plus_inv_sqrt_2); + final T coeffDot4 = theta.multiply(twoTheta.subtract(1)); + final T[] interpolatedState; + final T[] interpolatedDerivatives; + + if (getGlobalPreviousState() != null && theta.getReal() <= 0.5) { + final T s = thetaH.divide(6.0); + final T c23 = s.multiply(theta.multiply(6).subtract(fourTheta2)); + final T coeff1 = s.multiply(fourTheta2.subtract(theta.multiply(9)).add(6)); + final T coeff2 = c23.multiply(one_minus_inv_sqrt_2); + final T coeff3 = c23.multiply(one_plus_inv_sqrt_2); + final T coeff4 = s.multiply(fourTheta2.subtract(theta.multiply(3))); + interpolatedState = previousStateLinearCombination(coeff1, coeff2, coeff3, coeff4); + interpolatedDerivatives = derivativeLinearCombination(coeffDot1, coeffDot2, coeffDot3, coeffDot4); + } else { + final T s = oneMinusThetaH.divide(-6.0); + final T c23 = s.multiply(twoTheta.add(2).subtract(fourTheta2)); + final T coeff1 = s.multiply(fourTheta2.subtract(theta.multiply(5)).add(1)); + final T coeff2 = c23.multiply(one_minus_inv_sqrt_2); + final T coeff3 = c23.multiply(one_plus_inv_sqrt_2); + final T coeff4 = s.multiply(fourTheta2.add(theta).add(1)); + interpolatedState = currentStateLinearCombination(coeff1, coeff2, coeff3, coeff4); + interpolatedDerivatives = derivativeLinearCombination(coeffDot1, coeffDot2, coeffDot3, coeffDot4); + } + + return new FieldODEStateAndDerivative<T>(time, interpolatedState, interpolatedDerivatives); + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/GillIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/GillIntegrator.java new file mode 100644 index 0000000..5273e02 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/GillIntegrator.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.util.FastMath; + + +/** + * This class implements the Gill fourth order Runge-Kutta + * integrator for Ordinary Differential Equations . + + * <p>This method is an explicit Runge-Kutta method, its Butcher-array + * is the following one : + * <pre> + * 0 | 0 0 0 0 + * 1/2 | 1/2 0 0 0 + * 1/2 | (q-1)/2 (2-q)/2 0 0 + * 1 | 0 -q/2 (2+q)/2 0 + * |------------------------------- + * | 1/6 (2-q)/6 (2+q)/6 1/6 + * </pre> + * where q = sqrt(2)</p> + * + * @see EulerIntegrator + * @see ClassicalRungeKuttaIntegrator + * @see MidpointIntegrator + * @see ThreeEighthesIntegrator + * @see LutherIntegrator + * @since 1.2 + */ + +public class GillIntegrator extends RungeKuttaIntegrator { + + /** Time steps Butcher array. */ + private static final double[] STATIC_C = { + 1.0 / 2.0, 1.0 / 2.0, 1.0 + }; + + /** Internal weights Butcher array. */ + private static final double[][] STATIC_A = { + { 1.0 / 2.0 }, + { (FastMath.sqrt(2.0) - 1.0) / 2.0, (2.0 - FastMath.sqrt(2.0)) / 2.0 }, + { 0.0, -FastMath.sqrt(2.0) / 2.0, (2.0 + FastMath.sqrt(2.0)) / 2.0 } + }; + + /** Propagation weights Butcher array. */ + private static final double[] STATIC_B = { + 1.0 / 6.0, (2.0 - FastMath.sqrt(2.0)) / 6.0, (2.0 + FastMath.sqrt(2.0)) / 6.0, 1.0 / 6.0 + }; + + /** Simple constructor. + * Build a fourth-order Gill integrator with the given step. + * @param step integration step + */ + public GillIntegrator(final double step) { + super("Gill", STATIC_C, STATIC_A, STATIC_B, new GillStepInterpolator(), step); + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/GillStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/GillStepInterpolator.java new file mode 100644 index 0000000..72dec69 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/GillStepInterpolator.java @@ -0,0 +1,151 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.ode.sampling.StepInterpolator; +import org.apache.commons.math3.util.FastMath; + +/** + * This class implements a step interpolator for the Gill fourth + * order Runge-Kutta integrator. + * + * <p>This interpolator allows to compute dense output inside the last + * step computed. The interpolation equation is consistent with the + * integration scheme : + * <ul> + * <li>Using reference point at step start:<br> + * y(t<sub>n</sub> + θ h) = y (t<sub>n</sub>) + * + θ (h/6) [ (6 - 9 θ + 4 θ<sup>2</sup>) y'<sub>1</sub> + * + ( 6 θ - 4 θ<sup>2</sup>) ((1-1/√2) y'<sub>2</sub> + (1+1/√2)) y'<sub>3</sub>) + * + ( - 3 θ + 4 θ<sup>2</sup>) y'<sub>4</sub> + * ] + * </li> + * <li>Using reference point at step start:<br> + * y(t<sub>n</sub> + θ h) = y (t<sub>n</sub> + h) + * - (1 - θ) (h/6) [ (1 - 5 θ + 4 θ<sup>2</sup>) y'<sub>1</sub> + * + (2 + 2 θ - 4 θ<sup>2</sup>) ((1-1/√2) y'<sub>2</sub> + (1+1/√2)) y'<sub>3</sub>) + * + (1 + θ + 4 θ<sup>2</sup>) y'<sub>4</sub> + * ] + * </li> + * </ul> + * </p> + * where θ belongs to [0 ; 1] and where y'<sub>1</sub> to y'<sub>4</sub> + * are the four evaluations of the derivatives already computed during + * the step.</p> + * + * @see GillIntegrator + * @since 1.2 + */ + +class GillStepInterpolator + extends RungeKuttaStepInterpolator { + + /** First Gill coefficient. */ + private static final double ONE_MINUS_INV_SQRT_2 = 1 - FastMath.sqrt(0.5); + + /** Second Gill coefficient. */ + private static final double ONE_PLUS_INV_SQRT_2 = 1 + FastMath.sqrt(0.5); + + /** Serializable version identifier. */ + private static final long serialVersionUID = 20111120L; + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link + * org.apache.commons.math3.ode.sampling.AbstractStepInterpolator#reinitialize} + * method should be called before using the instance in order to + * initialize the internal arrays. This constructor is used only + * in order to delay the initialization in some cases. The {@link + * RungeKuttaIntegrator} class uses the prototyping design pattern + * to create the step interpolators by cloning an uninitialized model + * and later initializing the copy. + */ + // CHECKSTYLE: stop RedundantModifier + // the public modifier here is needed for serialization + public GillStepInterpolator() { + } + // CHECKSTYLE: resume RedundantModifier + + /** Copy constructor. + * @param interpolator interpolator to copy from. The copy is a deep + * copy: its arrays are separated from the original arrays of the + * instance + */ + GillStepInterpolator(final GillStepInterpolator interpolator) { + super(interpolator); + } + + /** {@inheritDoc} */ + @Override + protected StepInterpolator doCopy() { + return new GillStepInterpolator(this); + } + + + /** {@inheritDoc} */ + @Override + protected void computeInterpolatedStateAndDerivatives(final double theta, + final double oneMinusThetaH) { + + final double twoTheta = 2 * theta; + final double fourTheta2 = twoTheta * twoTheta; + final double coeffDot1 = theta * (twoTheta - 3) + 1; + final double cDot23 = twoTheta * (1 - theta); + final double coeffDot2 = cDot23 * ONE_MINUS_INV_SQRT_2; + final double coeffDot3 = cDot23 * ONE_PLUS_INV_SQRT_2; + final double coeffDot4 = theta * (twoTheta - 1); + + if ((previousState != null) && (theta <= 0.5)) { + final double s = theta * h / 6.0; + final double c23 = s * (6 * theta - fourTheta2); + final double coeff1 = s * (6 - 9 * theta + fourTheta2); + final double coeff2 = c23 * ONE_MINUS_INV_SQRT_2; + final double coeff3 = c23 * ONE_PLUS_INV_SQRT_2; + final double coeff4 = s * (-3 * theta + fourTheta2); + for (int i = 0; i < interpolatedState.length; ++i) { + final double yDot1 = yDotK[0][i]; + final double yDot2 = yDotK[1][i]; + final double yDot3 = yDotK[2][i]; + final double yDot4 = yDotK[3][i]; + interpolatedState[i] = + previousState[i] + coeff1 * yDot1 + coeff2 * yDot2 + coeff3 * yDot3 + coeff4 * yDot4; + interpolatedDerivatives[i] = + coeffDot1 * yDot1 + coeffDot2 * yDot2 + coeffDot3 * yDot3 + coeffDot4 * yDot4; + } + } else { + final double s = oneMinusThetaH / 6.0; + final double c23 = s * (2 + twoTheta - fourTheta2); + final double coeff1 = s * (1 - 5 * theta + fourTheta2); + final double coeff2 = c23 * ONE_MINUS_INV_SQRT_2; + final double coeff3 = c23 * ONE_PLUS_INV_SQRT_2; + final double coeff4 = s * (1 + theta + fourTheta2); + for (int i = 0; i < interpolatedState.length; ++i) { + final double yDot1 = yDotK[0][i]; + final double yDot2 = yDotK[1][i]; + final double yDot3 = yDotK[2][i]; + final double yDot4 = yDotK[3][i]; + interpolatedState[i] = + currentState[i] - coeff1 * yDot1 - coeff2 * yDot2 - coeff3 * yDot3 - coeff4 * yDot4; + interpolatedDerivatives[i] = + coeffDot1 * yDot1 + coeffDot2 * yDot2 + coeffDot3 * yDot3 + coeffDot4 * yDot4; + } + } + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/GraggBulirschStoerIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/GraggBulirschStoerIntegrator.java new file mode 100644 index 0000000..50a463e --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/GraggBulirschStoerIntegrator.java @@ -0,0 +1,949 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.analysis.solvers.UnivariateSolver; +import org.apache.commons.math3.exception.DimensionMismatchException; +import org.apache.commons.math3.exception.MaxCountExceededException; +import org.apache.commons.math3.exception.NoBracketingException; +import org.apache.commons.math3.exception.NumberIsTooSmallException; +import org.apache.commons.math3.ode.ExpandableStatefulODE; +import org.apache.commons.math3.ode.events.EventHandler; +import org.apache.commons.math3.ode.sampling.AbstractStepInterpolator; +import org.apache.commons.math3.ode.sampling.StepHandler; +import org.apache.commons.math3.util.FastMath; + +/** + * This class implements a Gragg-Bulirsch-Stoer integrator for + * Ordinary Differential Equations. + * + * <p>The Gragg-Bulirsch-Stoer algorithm is one of the most efficient + * ones currently available for smooth problems. It uses Richardson + * extrapolation to estimate what would be the solution if the step + * size could be decreased down to zero.</p> + * + * <p> + * This method changes both the step size and the order during + * integration, in order to minimize computation cost. It is + * particularly well suited when a very high precision is needed. The + * limit where this method becomes more efficient than high-order + * embedded Runge-Kutta methods like {@link DormandPrince853Integrator + * Dormand-Prince 8(5,3)} depends on the problem. Results given in the + * Hairer, Norsett and Wanner book show for example that this limit + * occurs for accuracy around 1e-6 when integrating Saltzam-Lorenz + * equations (the authors note this problem is <i>extremely sensitive + * to the errors in the first integration steps</i>), and around 1e-11 + * for a two dimensional celestial mechanics problems with seven + * bodies (pleiades problem, involving quasi-collisions for which + * <i>automatic step size control is essential</i>). + * </p> + * + * <p> + * This implementation is basically a reimplementation in Java of the + * <a + * href="http://www.unige.ch/math/folks/hairer/prog/nonstiff/odex.f">odex</a> + * fortran code by E. Hairer and G. Wanner. The redistribution policy + * for this code is available <a + * href="http://www.unige.ch/~hairer/prog/licence.txt">here</a>, for + * convenience, it is reproduced below.</p> + * </p> + * + * <table border="0" width="80%" cellpadding="10" align="center" bgcolor="#E0E0E0"> + * <tr><td>Copyright (c) 2004, Ernst Hairer</td></tr> + * + * <tr><td>Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * <ul> + * <li>Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer.</li> + * <li>Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution.</li> + * </ul></td></tr> + * + * <tr><td><strong>THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.</strong></td></tr> + * </table> + * + * @since 1.2 + */ + +public class GraggBulirschStoerIntegrator extends AdaptiveStepsizeIntegrator { + + /** Integrator method name. */ + private static final String METHOD_NAME = "Gragg-Bulirsch-Stoer"; + + /** maximal order. */ + private int maxOrder; + + /** step size sequence. */ + private int[] sequence; + + /** overall cost of applying step reduction up to iteration k+1, in number of calls. */ + private int[] costPerStep; + + /** cost per unit step. */ + private double[] costPerTimeUnit; + + /** optimal steps for each order. */ + private double[] optimalStep; + + /** extrapolation coefficients. */ + private double[][] coeff; + + /** stability check enabling parameter. */ + private boolean performTest; + + /** maximal number of checks for each iteration. */ + private int maxChecks; + + /** maximal number of iterations for which checks are performed. */ + private int maxIter; + + /** stepsize reduction factor in case of stability check failure. */ + private double stabilityReduction; + + /** first stepsize control factor. */ + private double stepControl1; + + /** second stepsize control factor. */ + private double stepControl2; + + /** third stepsize control factor. */ + private double stepControl3; + + /** fourth stepsize control factor. */ + private double stepControl4; + + /** first order control factor. */ + private double orderControl1; + + /** second order control factor. */ + private double orderControl2; + + /** use interpolation error in stepsize control. */ + private boolean useInterpolationError; + + /** interpolation order control parameter. */ + private int mudif; + + /** Simple constructor. + * Build a Gragg-Bulirsch-Stoer integrator with the given step + * bounds. All tuning parameters are set to their default + * values. The default step handler does nothing. + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + */ + public GraggBulirschStoerIntegrator(final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) { + super(METHOD_NAME, minStep, maxStep, + scalAbsoluteTolerance, scalRelativeTolerance); + setStabilityCheck(true, -1, -1, -1); + setControlFactors(-1, -1, -1, -1); + setOrderControl(-1, -1, -1); + setInterpolationControl(true, -1); + } + + /** Simple constructor. + * Build a Gragg-Bulirsch-Stoer integrator with the given step + * bounds. All tuning parameters are set to their default + * values. The default step handler does nothing. + * @param minStep minimal step (must be positive even for backward + * integration), the last step can be smaller than this + * @param maxStep maximal step (must be positive even for backward + * integration) + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + */ + public GraggBulirschStoerIntegrator(final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) { + super(METHOD_NAME, minStep, maxStep, + vecAbsoluteTolerance, vecRelativeTolerance); + setStabilityCheck(true, -1, -1, -1); + setControlFactors(-1, -1, -1, -1); + setOrderControl(-1, -1, -1); + setInterpolationControl(true, -1); + } + + /** Set the stability check controls. + * <p>The stability check is performed on the first few iterations of + * the extrapolation scheme. If this test fails, the step is rejected + * and the stepsize is reduced.</p> + * <p>By default, the test is performed, at most during two + * iterations at each step, and at most once for each of these + * iterations. The default stepsize reduction factor is 0.5.</p> + * @param performStabilityCheck if true, stability check will be performed, + if false, the check will be skipped + * @param maxNumIter maximal number of iterations for which checks are + * performed (the number of iterations is reset to default if negative + * or null) + * @param maxNumChecks maximal number of checks for each iteration + * (the number of checks is reset to default if negative or null) + * @param stepsizeReductionFactor stepsize reduction factor in case of + * failure (the factor is reset to default if lower than 0.0001 or + * greater than 0.9999) + */ + public void setStabilityCheck(final boolean performStabilityCheck, + final int maxNumIter, final int maxNumChecks, + final double stepsizeReductionFactor) { + + this.performTest = performStabilityCheck; + this.maxIter = (maxNumIter <= 0) ? 2 : maxNumIter; + this.maxChecks = (maxNumChecks <= 0) ? 1 : maxNumChecks; + + if ((stepsizeReductionFactor < 0.0001) || (stepsizeReductionFactor > 0.9999)) { + this.stabilityReduction = 0.5; + } else { + this.stabilityReduction = stepsizeReductionFactor; + } + + } + + /** Set the step size control factors. + + * <p>The new step size hNew is computed from the old one h by: + * <pre> + * hNew = h * stepControl2 / (err/stepControl1)^(1/(2k+1)) + * </pre> + * where err is the scaled error and k the iteration number of the + * extrapolation scheme (counting from 0). The default values are + * 0.65 for stepControl1 and 0.94 for stepControl2.</p> + * <p>The step size is subject to the restriction: + * <pre> + * stepControl3^(1/(2k+1))/stepControl4 <= hNew/h <= 1/stepControl3^(1/(2k+1)) + * </pre> + * The default values are 0.02 for stepControl3 and 4.0 for + * stepControl4.</p> + * @param control1 first stepsize control factor (the factor is + * reset to default if lower than 0.0001 or greater than 0.9999) + * @param control2 second stepsize control factor (the factor + * is reset to default if lower than 0.0001 or greater than 0.9999) + * @param control3 third stepsize control factor (the factor is + * reset to default if lower than 0.0001 or greater than 0.9999) + * @param control4 fourth stepsize control factor (the factor + * is reset to default if lower than 1.0001 or greater than 999.9) + */ + public void setControlFactors(final double control1, final double control2, + final double control3, final double control4) { + + if ((control1 < 0.0001) || (control1 > 0.9999)) { + this.stepControl1 = 0.65; + } else { + this.stepControl1 = control1; + } + + if ((control2 < 0.0001) || (control2 > 0.9999)) { + this.stepControl2 = 0.94; + } else { + this.stepControl2 = control2; + } + + if ((control3 < 0.0001) || (control3 > 0.9999)) { + this.stepControl3 = 0.02; + } else { + this.stepControl3 = control3; + } + + if ((control4 < 1.0001) || (control4 > 999.9)) { + this.stepControl4 = 4.0; + } else { + this.stepControl4 = control4; + } + + } + + /** Set the order control parameters. + * <p>The Gragg-Bulirsch-Stoer method changes both the step size and + * the order during integration, in order to minimize computation + * cost. Each extrapolation step increases the order by 2, so the + * maximal order that will be used is always even, it is twice the + * maximal number of columns in the extrapolation table.</p> + * <pre> + * order is decreased if w(k-1) <= w(k) * orderControl1 + * order is increased if w(k) <= w(k-1) * orderControl2 + * </pre> + * <p>where w is the table of work per unit step for each order + * (number of function calls divided by the step length), and k is + * the current order.</p> + * <p>The default maximal order after construction is 18 (i.e. the + * maximal number of columns is 9). The default values are 0.8 for + * orderControl1 and 0.9 for orderControl2.</p> + * @param maximalOrder maximal order in the extrapolation table (the + * maximal order is reset to default if order <= 6 or odd) + * @param control1 first order control factor (the factor is + * reset to default if lower than 0.0001 or greater than 0.9999) + * @param control2 second order control factor (the factor + * is reset to default if lower than 0.0001 or greater than 0.9999) + */ + public void setOrderControl(final int maximalOrder, + final double control1, final double control2) { + + if ((maximalOrder <= 6) || (maximalOrder % 2 != 0)) { + this.maxOrder = 18; + } + + if ((control1 < 0.0001) || (control1 > 0.9999)) { + this.orderControl1 = 0.8; + } else { + this.orderControl1 = control1; + } + + if ((control2 < 0.0001) || (control2 > 0.9999)) { + this.orderControl2 = 0.9; + } else { + this.orderControl2 = control2; + } + + // reinitialize the arrays + initializeArrays(); + + } + + /** {@inheritDoc} */ + @Override + public void addStepHandler (final StepHandler handler) { + + super.addStepHandler(handler); + + // reinitialize the arrays + initializeArrays(); + + } + + /** {@inheritDoc} */ + @Override + public void addEventHandler(final EventHandler function, + final double maxCheckInterval, + final double convergence, + final int maxIterationCount, + final UnivariateSolver solver) { + super.addEventHandler(function, maxCheckInterval, convergence, + maxIterationCount, solver); + + // reinitialize the arrays + initializeArrays(); + + } + + /** Initialize the integrator internal arrays. */ + private void initializeArrays() { + + final int size = maxOrder / 2; + + if ((sequence == null) || (sequence.length != size)) { + // all arrays should be reallocated with the right size + sequence = new int[size]; + costPerStep = new int[size]; + coeff = new double[size][]; + costPerTimeUnit = new double[size]; + optimalStep = new double[size]; + } + + // step size sequence: 2, 6, 10, 14, ... + for (int k = 0; k < size; ++k) { + sequence[k] = 4 * k + 2; + } + + // initialize the order selection cost array + // (number of function calls for each column of the extrapolation table) + costPerStep[0] = sequence[0] + 1; + for (int k = 1; k < size; ++k) { + costPerStep[k] = costPerStep[k-1] + sequence[k]; + } + + // initialize the extrapolation tables + for (int k = 0; k < size; ++k) { + coeff[k] = (k > 0) ? new double[k] : null; + for (int l = 0; l < k; ++l) { + final double ratio = ((double) sequence[k]) / sequence[k-l-1]; + coeff[k][l] = 1.0 / (ratio * ratio - 1.0); + } + } + + } + + /** Set the interpolation order control parameter. + * The interpolation order for dense output is 2k - mudif + 1. The + * default value for mudif is 4 and the interpolation error is used + * in stepsize control by default. + + * @param useInterpolationErrorForControl if true, interpolation error is used + * for stepsize control + * @param mudifControlParameter interpolation order control parameter (the parameter + * is reset to default if <= 0 or >= 7) + */ + public void setInterpolationControl(final boolean useInterpolationErrorForControl, + final int mudifControlParameter) { + + this.useInterpolationError = useInterpolationErrorForControl; + + if ((mudifControlParameter <= 0) || (mudifControlParameter >= 7)) { + this.mudif = 4; + } else { + this.mudif = mudifControlParameter; + } + + } + + /** Update scaling array. + * @param y1 first state vector to use for scaling + * @param y2 second state vector to use for scaling + * @param scale scaling array to update (can be shorter than state) + */ + private void rescale(final double[] y1, final double[] y2, final double[] scale) { + if (vecAbsoluteTolerance == null) { + for (int i = 0; i < scale.length; ++i) { + final double yi = FastMath.max(FastMath.abs(y1[i]), FastMath.abs(y2[i])); + scale[i] = scalAbsoluteTolerance + scalRelativeTolerance * yi; + } + } else { + for (int i = 0; i < scale.length; ++i) { + final double yi = FastMath.max(FastMath.abs(y1[i]), FastMath.abs(y2[i])); + scale[i] = vecAbsoluteTolerance[i] + vecRelativeTolerance[i] * yi; + } + } + } + + /** Perform integration over one step using substeps of a modified + * midpoint method. + * @param t0 initial time + * @param y0 initial value of the state vector at t0 + * @param step global step + * @param k iteration number (from 0 to sequence.length - 1) + * @param scale scaling array (can be shorter than state) + * @param f placeholder where to put the state vector derivatives at each substep + * (element 0 already contains initial derivative) + * @param yMiddle placeholder where to put the state vector at the middle of the step + * @param yEnd placeholder where to put the state vector at the end + * @param yTmp placeholder for one state vector + * @return true if computation was done properly, + * false if stability check failed before end of computation + * @exception MaxCountExceededException if the number of functions evaluations is exceeded + * @exception DimensionMismatchException if arrays dimensions do not match equations settings + */ + private boolean tryStep(final double t0, final double[] y0, final double step, final int k, + final double[] scale, final double[][] f, + final double[] yMiddle, final double[] yEnd, + final double[] yTmp) + throws MaxCountExceededException, DimensionMismatchException { + + final int n = sequence[k]; + final double subStep = step / n; + final double subStep2 = 2 * subStep; + + // first substep + double t = t0 + subStep; + for (int i = 0; i < y0.length; ++i) { + yTmp[i] = y0[i]; + yEnd[i] = y0[i] + subStep * f[0][i]; + } + computeDerivatives(t, yEnd, f[1]); + + // other substeps + for (int j = 1; j < n; ++j) { + + if (2 * j == n) { + // save the point at the middle of the step + System.arraycopy(yEnd, 0, yMiddle, 0, y0.length); + } + + t += subStep; + for (int i = 0; i < y0.length; ++i) { + final double middle = yEnd[i]; + yEnd[i] = yTmp[i] + subStep2 * f[j][i]; + yTmp[i] = middle; + } + + computeDerivatives(t, yEnd, f[j+1]); + + // stability check + if (performTest && (j <= maxChecks) && (k < maxIter)) { + double initialNorm = 0.0; + for (int l = 0; l < scale.length; ++l) { + final double ratio = f[0][l] / scale[l]; + initialNorm += ratio * ratio; + } + double deltaNorm = 0.0; + for (int l = 0; l < scale.length; ++l) { + final double ratio = (f[j+1][l] - f[0][l]) / scale[l]; + deltaNorm += ratio * ratio; + } + if (deltaNorm > 4 * FastMath.max(1.0e-15, initialNorm)) { + return false; + } + } + + } + + // correction of the last substep (at t0 + step) + for (int i = 0; i < y0.length; ++i) { + yEnd[i] = 0.5 * (yTmp[i] + yEnd[i] + subStep * f[n][i]); + } + + return true; + + } + + /** Extrapolate a vector. + * @param offset offset to use in the coefficients table + * @param k index of the last updated point + * @param diag working diagonal of the Aitken-Neville's + * triangle, without the last element + * @param last last element + */ + private void extrapolate(final int offset, final int k, + final double[][] diag, final double[] last) { + + // update the diagonal + for (int j = 1; j < k; ++j) { + for (int i = 0; i < last.length; ++i) { + // Aitken-Neville's recursive formula + diag[k-j-1][i] = diag[k-j][i] + + coeff[k+offset][j-1] * (diag[k-j][i] - diag[k-j-1][i]); + } + } + + // update the last element + for (int i = 0; i < last.length; ++i) { + // Aitken-Neville's recursive formula + last[i] = diag[0][i] + coeff[k+offset][k-1] * (diag[0][i] - last[i]); + } + } + + /** {@inheritDoc} */ + @Override + public void integrate(final ExpandableStatefulODE equations, final double t) + throws NumberIsTooSmallException, DimensionMismatchException, + MaxCountExceededException, NoBracketingException { + + sanityChecks(equations, t); + setEquations(equations); + final boolean forward = t > equations.getTime(); + + // create some internal working arrays + final double[] y0 = equations.getCompleteState(); + final double[] y = y0.clone(); + final double[] yDot0 = new double[y.length]; + final double[] y1 = new double[y.length]; + final double[] yTmp = new double[y.length]; + final double[] yTmpDot = new double[y.length]; + + final double[][] diagonal = new double[sequence.length-1][]; + final double[][] y1Diag = new double[sequence.length-1][]; + for (int k = 0; k < sequence.length-1; ++k) { + diagonal[k] = new double[y.length]; + y1Diag[k] = new double[y.length]; + } + + final double[][][] fk = new double[sequence.length][][]; + for (int k = 0; k < sequence.length; ++k) { + + fk[k] = new double[sequence[k] + 1][]; + + // all substeps start at the same point, so share the first array + fk[k][0] = yDot0; + + for (int l = 0; l < sequence[k]; ++l) { + fk[k][l+1] = new double[y0.length]; + } + + } + + if (y != y0) { + System.arraycopy(y0, 0, y, 0, y0.length); + } + + final double[] yDot1 = new double[y0.length]; + final double[][] yMidDots = new double[1 + 2 * sequence.length][y0.length]; + + // initial scaling + final double[] scale = new double[mainSetDimension]; + rescale(y, y, scale); + + // initial order selection + final double tol = + (vecRelativeTolerance == null) ? scalRelativeTolerance : vecRelativeTolerance[0]; + final double log10R = FastMath.log10(FastMath.max(1.0e-10, tol)); + int targetIter = FastMath.max(1, + FastMath.min(sequence.length - 2, + (int) FastMath.floor(0.5 - 0.6 * log10R))); + + // set up an interpolator sharing the integrator arrays + final AbstractStepInterpolator interpolator = + new GraggBulirschStoerStepInterpolator(y, yDot0, + y1, yDot1, + yMidDots, forward, + equations.getPrimaryMapper(), + equations.getSecondaryMappers()); + interpolator.storeTime(equations.getTime()); + + stepStart = equations.getTime(); + double hNew = 0; + double maxError = Double.MAX_VALUE; + boolean previousRejected = false; + boolean firstTime = true; + boolean newStep = true; + boolean firstStepAlreadyComputed = false; + initIntegration(equations.getTime(), y0, t); + costPerTimeUnit[0] = 0; + isLastStep = false; + do { + + double error; + boolean reject = false; + + if (newStep) { + + interpolator.shift(); + + // first evaluation, at the beginning of the step + if (! firstStepAlreadyComputed) { + computeDerivatives(stepStart, y, yDot0); + } + + if (firstTime) { + hNew = initializeStep(forward, 2 * targetIter + 1, scale, + stepStart, y, yDot0, yTmp, yTmpDot); + } + + newStep = false; + + } + + stepSize = hNew; + + // step adjustment near bounds + if ((forward && (stepStart + stepSize > t)) || + ((! forward) && (stepStart + stepSize < t))) { + stepSize = t - stepStart; + } + final double nextT = stepStart + stepSize; + isLastStep = forward ? (nextT >= t) : (nextT <= t); + + // iterate over several substep sizes + int k = -1; + for (boolean loop = true; loop; ) { + + ++k; + + // modified midpoint integration with the current substep + if ( ! tryStep(stepStart, y, stepSize, k, scale, fk[k], + (k == 0) ? yMidDots[0] : diagonal[k-1], + (k == 0) ? y1 : y1Diag[k-1], + yTmp)) { + + // the stability check failed, we reduce the global step + hNew = FastMath.abs(filterStep(stepSize * stabilityReduction, forward, false)); + reject = true; + loop = false; + + } else { + + // the substep was computed successfully + if (k > 0) { + + // extrapolate the state at the end of the step + // using last iteration data + extrapolate(0, k, y1Diag, y1); + rescale(y, y1, scale); + + // estimate the error at the end of the step. + error = 0; + for (int j = 0; j < mainSetDimension; ++j) { + final double e = FastMath.abs(y1[j] - y1Diag[0][j]) / scale[j]; + error += e * e; + } + error = FastMath.sqrt(error / mainSetDimension); + + if ((error > 1.0e15) || ((k > 1) && (error > maxError))) { + // error is too big, we reduce the global step + hNew = FastMath.abs(filterStep(stepSize * stabilityReduction, forward, false)); + reject = true; + loop = false; + } else { + + maxError = FastMath.max(4 * error, 1.0); + + // compute optimal stepsize for this order + final double exp = 1.0 / (2 * k + 1); + double fac = stepControl2 / FastMath.pow(error / stepControl1, exp); + final double pow = FastMath.pow(stepControl3, exp); + fac = FastMath.max(pow / stepControl4, FastMath.min(1 / pow, fac)); + optimalStep[k] = FastMath.abs(filterStep(stepSize * fac, forward, true)); + costPerTimeUnit[k] = costPerStep[k] / optimalStep[k]; + + // check convergence + switch (k - targetIter) { + + case -1 : + if ((targetIter > 1) && ! previousRejected) { + + // check if we can stop iterations now + if (error <= 1.0) { + // convergence have been reached just before targetIter + loop = false; + } else { + // estimate if there is a chance convergence will + // be reached on next iteration, using the + // asymptotic evolution of error + final double ratio = ((double) sequence [targetIter] * sequence[targetIter + 1]) / + (sequence[0] * sequence[0]); + if (error > ratio * ratio) { + // we don't expect to converge on next iteration + // we reject the step immediately and reduce order + reject = true; + loop = false; + targetIter = k; + if ((targetIter > 1) && + (costPerTimeUnit[targetIter-1] < + orderControl1 * costPerTimeUnit[targetIter])) { + --targetIter; + } + hNew = optimalStep[targetIter]; + } + } + } + break; + + case 0: + if (error <= 1.0) { + // convergence has been reached exactly at targetIter + loop = false; + } else { + // estimate if there is a chance convergence will + // be reached on next iteration, using the + // asymptotic evolution of error + final double ratio = ((double) sequence[k+1]) / sequence[0]; + if (error > ratio * ratio) { + // we don't expect to converge on next iteration + // we reject the step immediately + reject = true; + loop = false; + if ((targetIter > 1) && + (costPerTimeUnit[targetIter-1] < + orderControl1 * costPerTimeUnit[targetIter])) { + --targetIter; + } + hNew = optimalStep[targetIter]; + } + } + break; + + case 1 : + if (error > 1.0) { + reject = true; + if ((targetIter > 1) && + (costPerTimeUnit[targetIter-1] < + orderControl1 * costPerTimeUnit[targetIter])) { + --targetIter; + } + hNew = optimalStep[targetIter]; + } + loop = false; + break; + + default : + if ((firstTime || isLastStep) && (error <= 1.0)) { + loop = false; + } + break; + + } + + } + } + } + } + + if (! reject) { + // derivatives at end of step + computeDerivatives(stepStart + stepSize, y1, yDot1); + } + + // dense output handling + double hInt = getMaxStep(); + if (! reject) { + + // extrapolate state at middle point of the step + for (int j = 1; j <= k; ++j) { + extrapolate(0, j, diagonal, yMidDots[0]); + } + + final int mu = 2 * k - mudif + 3; + + for (int l = 0; l < mu; ++l) { + + // derivative at middle point of the step + final int l2 = l / 2; + double factor = FastMath.pow(0.5 * sequence[l2], l); + int middleIndex = fk[l2].length / 2; + for (int i = 0; i < y0.length; ++i) { + yMidDots[l+1][i] = factor * fk[l2][middleIndex + l][i]; + } + for (int j = 1; j <= k - l2; ++j) { + factor = FastMath.pow(0.5 * sequence[j + l2], l); + middleIndex = fk[l2+j].length / 2; + for (int i = 0; i < y0.length; ++i) { + diagonal[j-1][i] = factor * fk[l2+j][middleIndex+l][i]; + } + extrapolate(l2, j, diagonal, yMidDots[l+1]); + } + for (int i = 0; i < y0.length; ++i) { + yMidDots[l+1][i] *= stepSize; + } + + // compute centered differences to evaluate next derivatives + for (int j = (l + 1) / 2; j <= k; ++j) { + for (int m = fk[j].length - 1; m >= 2 * (l + 1); --m) { + for (int i = 0; i < y0.length; ++i) { + fk[j][m][i] -= fk[j][m-2][i]; + } + } + } + + } + + if (mu >= 0) { + + // estimate the dense output coefficients + final GraggBulirschStoerStepInterpolator gbsInterpolator + = (GraggBulirschStoerStepInterpolator) interpolator; + gbsInterpolator.computeCoefficients(mu, stepSize); + + if (useInterpolationError) { + // use the interpolation error to limit stepsize + final double interpError = gbsInterpolator.estimateError(scale); + hInt = FastMath.abs(stepSize / FastMath.max(FastMath.pow(interpError, 1.0 / (mu+4)), + 0.01)); + if (interpError > 10.0) { + hNew = hInt; + reject = true; + } + } + + } + + } + + if (! reject) { + + // Discrete events handling + interpolator.storeTime(stepStart + stepSize); + stepStart = acceptStep(interpolator, y1, yDot1, t); + + // prepare next step + interpolator.storeTime(stepStart); + System.arraycopy(y1, 0, y, 0, y0.length); + System.arraycopy(yDot1, 0, yDot0, 0, y0.length); + firstStepAlreadyComputed = true; + + int optimalIter; + if (k == 1) { + optimalIter = 2; + if (previousRejected) { + optimalIter = 1; + } + } else if (k <= targetIter) { + optimalIter = k; + if (costPerTimeUnit[k-1] < orderControl1 * costPerTimeUnit[k]) { + optimalIter = k-1; + } else if (costPerTimeUnit[k] < orderControl2 * costPerTimeUnit[k-1]) { + optimalIter = FastMath.min(k+1, sequence.length - 2); + } + } else { + optimalIter = k - 1; + if ((k > 2) && + (costPerTimeUnit[k-2] < orderControl1 * costPerTimeUnit[k-1])) { + optimalIter = k - 2; + } + if (costPerTimeUnit[k] < orderControl2 * costPerTimeUnit[optimalIter]) { + optimalIter = FastMath.min(k, sequence.length - 2); + } + } + + if (previousRejected) { + // after a rejected step neither order nor stepsize + // should increase + targetIter = FastMath.min(optimalIter, k); + hNew = FastMath.min(FastMath.abs(stepSize), optimalStep[targetIter]); + } else { + // stepsize control + if (optimalIter <= k) { + hNew = optimalStep[optimalIter]; + } else { + if ((k < targetIter) && + (costPerTimeUnit[k] < orderControl2 * costPerTimeUnit[k-1])) { + hNew = filterStep(optimalStep[k] * costPerStep[optimalIter+1] / costPerStep[k], + forward, false); + } else { + hNew = filterStep(optimalStep[k] * costPerStep[optimalIter] / costPerStep[k], + forward, false); + } + } + + targetIter = optimalIter; + + } + + newStep = true; + + } + + hNew = FastMath.min(hNew, hInt); + if (! forward) { + hNew = -hNew; + } + + firstTime = false; + + if (reject) { + isLastStep = false; + previousRejected = true; + } else { + previousRejected = false; + } + + } while (!isLastStep); + + // dispatch results + equations.setTime(stepStart); + equations.setCompleteState(y); + + resetInternalState(); + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/GraggBulirschStoerStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/GraggBulirschStoerStepInterpolator.java new file mode 100644 index 0000000..33c2705 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/GraggBulirschStoerStepInterpolator.java @@ -0,0 +1,407 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +import org.apache.commons.math3.ode.EquationsMapper; +import org.apache.commons.math3.ode.sampling.AbstractStepInterpolator; +import org.apache.commons.math3.ode.sampling.StepInterpolator; +import org.apache.commons.math3.util.FastMath; + +/** + * This class implements an interpolator for the Gragg-Bulirsch-Stoer + * integrator. + * + * <p>This interpolator compute dense output inside the last step + * produced by a Gragg-Bulirsch-Stoer integrator.</p> + * + * <p> + * This implementation is basically a reimplementation in Java of the + * <a + * href="http://www.unige.ch/math/folks/hairer/prog/nonstiff/odex.f">odex</a> + * fortran code by E. Hairer and G. Wanner. The redistribution policy + * for this code is available <a + * href="http://www.unige.ch/~hairer/prog/licence.txt">here</a>, for + * convenience, it is reproduced below.</p> + * </p> + * + * <table border="0" width="80%" cellpadding="10" align="center" bgcolor="#E0E0E0"> + * <tr><td>Copyright (c) 2004, Ernst Hairer</td></tr> + * + * <tr><td>Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * <ul> + * <li>Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer.</li> + * <li>Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution.</li> + * </ul></td></tr> + * + * <tr><td><strong>THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.</strong></td></tr> + * </table> + * + * @see GraggBulirschStoerIntegrator + * @since 1.2 + */ + +class GraggBulirschStoerStepInterpolator + extends AbstractStepInterpolator { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 20110928L; + + /** Slope at the beginning of the step. */ + private double[] y0Dot; + + /** State at the end of the step. */ + private double[] y1; + + /** Slope at the end of the step. */ + private double[] y1Dot; + + /** Derivatives at the middle of the step. + * element 0 is state at midpoint, element 1 is first derivative ... + */ + private double[][] yMidDots; + + /** Interpolation polynomials. */ + private double[][] polynomials; + + /** Error coefficients for the interpolation. */ + private double[] errfac; + + /** Degree of the interpolation polynomials. */ + private int currentDegree; + + /** Simple constructor. + * This constructor should not be used directly, it is only intended + * for the serialization process. + */ + // CHECKSTYLE: stop RedundantModifier + // the public modifier here is needed for serialization + public GraggBulirschStoerStepInterpolator() { + y0Dot = null; + y1 = null; + y1Dot = null; + yMidDots = null; + resetTables(-1); + } + // CHECKSTYLE: resume RedundantModifier + + /** Simple constructor. + * @param y reference to the integrator array holding the current state + * @param y0Dot reference to the integrator array holding the slope + * at the beginning of the step + * @param y1 reference to the integrator array holding the state at + * the end of the step + * @param y1Dot reference to the integrator array holding the slope + * at the end of the step + * @param yMidDots reference to the integrator array holding the + * derivatives at the middle point of the step + * @param forward integration direction indicator + * @param primaryMapper equations mapper for the primary equations set + * @param secondaryMappers equations mappers for the secondary equations sets + */ + GraggBulirschStoerStepInterpolator(final double[] y, final double[] y0Dot, + final double[] y1, final double[] y1Dot, + final double[][] yMidDots, + final boolean forward, + final EquationsMapper primaryMapper, + final EquationsMapper[] secondaryMappers) { + + super(y, forward, primaryMapper, secondaryMappers); + this.y0Dot = y0Dot; + this.y1 = y1; + this.y1Dot = y1Dot; + this.yMidDots = yMidDots; + + resetTables(yMidDots.length + 4); + + } + + /** Copy constructor. + * @param interpolator interpolator to copy from. The copy is a deep + * copy: its arrays are separated from the original arrays of the + * instance + */ + GraggBulirschStoerStepInterpolator(final GraggBulirschStoerStepInterpolator interpolator) { + + super(interpolator); + + final int dimension = currentState.length; + + // the interpolator has been finalized, + // the following arrays are not needed anymore + y0Dot = null; + y1 = null; + y1Dot = null; + yMidDots = null; + + // copy the interpolation polynomials (up to the current degree only) + if (interpolator.polynomials == null) { + polynomials = null; + currentDegree = -1; + } else { + resetTables(interpolator.currentDegree); + for (int i = 0; i < polynomials.length; ++i) { + polynomials[i] = new double[dimension]; + System.arraycopy(interpolator.polynomials[i], 0, + polynomials[i], 0, dimension); + } + currentDegree = interpolator.currentDegree; + } + + } + + /** Reallocate the internal tables. + * Reallocate the internal tables in order to be able to handle + * interpolation polynomials up to the given degree + * @param maxDegree maximal degree to handle + */ + private void resetTables(final int maxDegree) { + + if (maxDegree < 0) { + polynomials = null; + errfac = null; + currentDegree = -1; + } else { + + final double[][] newPols = new double[maxDegree + 1][]; + if (polynomials != null) { + System.arraycopy(polynomials, 0, newPols, 0, polynomials.length); + for (int i = polynomials.length; i < newPols.length; ++i) { + newPols[i] = new double[currentState.length]; + } + } else { + for (int i = 0; i < newPols.length; ++i) { + newPols[i] = new double[currentState.length]; + } + } + polynomials = newPols; + + // initialize the error factors array for interpolation + if (maxDegree <= 4) { + errfac = null; + } else { + errfac = new double[maxDegree - 4]; + for (int i = 0; i < errfac.length; ++i) { + final int ip5 = i + 5; + errfac[i] = 1.0 / (ip5 * ip5); + final double e = 0.5 * FastMath.sqrt (((double) (i + 1)) / ip5); + for (int j = 0; j <= i; ++j) { + errfac[i] *= e / (j + 1); + } + } + } + + currentDegree = 0; + + } + + } + + /** {@inheritDoc} */ + @Override + protected StepInterpolator doCopy() { + return new GraggBulirschStoerStepInterpolator(this); + } + + + /** Compute the interpolation coefficients for dense output. + * @param mu degree of the interpolation polynomial + * @param h current step + */ + public void computeCoefficients(final int mu, final double h) { + + if ((polynomials == null) || (polynomials.length <= (mu + 4))) { + resetTables(mu + 4); + } + + currentDegree = mu + 4; + + for (int i = 0; i < currentState.length; ++i) { + + final double yp0 = h * y0Dot[i]; + final double yp1 = h * y1Dot[i]; + final double ydiff = y1[i] - currentState[i]; + final double aspl = ydiff - yp1; + final double bspl = yp0 - ydiff; + + polynomials[0][i] = currentState[i]; + polynomials[1][i] = ydiff; + polynomials[2][i] = aspl; + polynomials[3][i] = bspl; + + if (mu < 0) { + return; + } + + // compute the remaining coefficients + final double ph0 = 0.5 * (currentState[i] + y1[i]) + 0.125 * (aspl + bspl); + polynomials[4][i] = 16 * (yMidDots[0][i] - ph0); + + if (mu > 0) { + final double ph1 = ydiff + 0.25 * (aspl - bspl); + polynomials[5][i] = 16 * (yMidDots[1][i] - ph1); + + if (mu > 1) { + final double ph2 = yp1 - yp0; + polynomials[6][i] = 16 * (yMidDots[2][i] - ph2 + polynomials[4][i]); + + if (mu > 2) { + final double ph3 = 6 * (bspl - aspl); + polynomials[7][i] = 16 * (yMidDots[3][i] - ph3 + 3 * polynomials[5][i]); + + for (int j = 4; j <= mu; ++j) { + final double fac1 = 0.5 * j * (j - 1); + final double fac2 = 2 * fac1 * (j - 2) * (j - 3); + polynomials[j+4][i] = + 16 * (yMidDots[j][i] + fac1 * polynomials[j+2][i] - fac2 * polynomials[j][i]); + } + + } + } + } + } + + } + + /** Estimate interpolation error. + * @param scale scaling array + * @return estimate of the interpolation error + */ + public double estimateError(final double[] scale) { + double error = 0; + if (currentDegree >= 5) { + for (int i = 0; i < scale.length; ++i) { + final double e = polynomials[currentDegree][i] / scale[i]; + error += e * e; + } + error = FastMath.sqrt(error / scale.length) * errfac[currentDegree - 5]; + } + return error; + } + + /** {@inheritDoc} */ + @Override + protected void computeInterpolatedStateAndDerivatives(final double theta, + final double oneMinusThetaH) { + + final int dimension = currentState.length; + + final double oneMinusTheta = 1.0 - theta; + final double theta05 = theta - 0.5; + final double tOmT = theta * oneMinusTheta; + final double t4 = tOmT * tOmT; + final double t4Dot = 2 * tOmT * (1 - 2 * theta); + final double dot1 = 1.0 / h; + final double dot2 = theta * (2 - 3 * theta) / h; + final double dot3 = ((3 * theta - 4) * theta + 1) / h; + + for (int i = 0; i < dimension; ++i) { + + final double p0 = polynomials[0][i]; + final double p1 = polynomials[1][i]; + final double p2 = polynomials[2][i]; + final double p3 = polynomials[3][i]; + interpolatedState[i] = p0 + theta * (p1 + oneMinusTheta * (p2 * theta + p3 * oneMinusTheta)); + interpolatedDerivatives[i] = dot1 * p1 + dot2 * p2 + dot3 * p3; + + if (currentDegree > 3) { + double cDot = 0; + double c = polynomials[currentDegree][i]; + for (int j = currentDegree - 1; j > 3; --j) { + final double d = 1.0 / (j - 3); + cDot = d * (theta05 * cDot + c); + c = polynomials[j][i] + c * d * theta05; + } + interpolatedState[i] += t4 * c; + interpolatedDerivatives[i] += (t4 * cDot + t4Dot * c) / h; + } + + } + + if (h == 0) { + // in this degenerated case, the previous computation leads to NaN for derivatives + // we fix this by using the derivatives at midpoint + System.arraycopy(yMidDots[1], 0, interpolatedDerivatives, 0, dimension); + } + + } + + /** {@inheritDoc} */ + @Override + public void writeExternal(final ObjectOutput out) + throws IOException { + + final int dimension = (currentState == null) ? -1 : currentState.length; + + // save the state of the base class + writeBaseExternal(out); + + // save the local attributes (but not the temporary vectors) + out.writeInt(currentDegree); + for (int k = 0; k <= currentDegree; ++k) { + for (int l = 0; l < dimension; ++l) { + out.writeDouble(polynomials[k][l]); + } + } + + } + + /** {@inheritDoc} */ + @Override + public void readExternal(final ObjectInput in) + throws IOException, ClassNotFoundException { + + // read the base class + final double t = readBaseExternal(in); + final int dimension = (currentState == null) ? -1 : currentState.length; + + // read the local attributes + final int degree = in.readInt(); + resetTables(degree); + currentDegree = degree; + + for (int k = 0; k <= currentDegree; ++k) { + for (int l = 0; l < dimension; ++l) { + polynomials[k][l] = in.readDouble(); + } + } + + // we can now set the interpolated time and state + setInterpolatedTime(t); + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/HighamHall54FieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/HighamHall54FieldIntegrator.java new file mode 100644 index 0000000..d7d6f63 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/HighamHall54FieldIntegrator.java @@ -0,0 +1,205 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.Field; +import org.apache.commons.math3.RealFieldElement; +import org.apache.commons.math3.ode.FieldEquationsMapper; +import org.apache.commons.math3.ode.FieldODEStateAndDerivative; +import org.apache.commons.math3.util.MathArrays; +import org.apache.commons.math3.util.MathUtils; + + +/** + * This class implements the 5(4) Higham and Hall integrator for + * Ordinary Differential Equations. + * + * <p>This integrator is an embedded Runge-Kutta integrator + * of order 5(4) used in local extrapolation mode (i.e. the solution + * is computed using the high order formula) with stepsize control + * (and automatic step initialization) and continuous output. This + * method uses 7 functions evaluations per step.</p> + * + * @param <T> the type of the field elements + * @since 3.6 + */ + +public class HighamHall54FieldIntegrator<T extends RealFieldElement<T>> + extends EmbeddedRungeKuttaFieldIntegrator<T> { + + /** Integrator method name. */ + private static final String METHOD_NAME = "Higham-Hall 5(4)"; + + /** Error weights Butcher array. */ + private final T[] e ; + + /** Simple constructor. + * Build a fifth order Higham and Hall integrator with the given step bounds + * @param field field to which the time and state vector elements belong + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + */ + public HighamHall54FieldIntegrator(final Field<T> field, + final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) { + super(field, METHOD_NAME, -1, + minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance); + e = MathArrays.buildArray(field, 7); + e[0] = fraction(-1, 20); + e[1] = field.getZero(); + e[2] = fraction(81, 160); + e[3] = fraction(-6, 5); + e[4] = fraction(25, 32); + e[5] = fraction( 1, 16); + e[6] = fraction(-1, 10); + } + + /** Simple constructor. + * Build a fifth order Higham and Hall integrator with the given step bounds + * @param field field to which the time and state vector elements belong + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + */ + public HighamHall54FieldIntegrator(final Field<T> field, + final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) { + super(field, METHOD_NAME, -1, + minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance); + e = MathArrays.buildArray(field, 7); + e[0] = fraction(-1, 20); + e[1] = field.getZero(); + e[2] = fraction(81, 160); + e[3] = fraction(-6, 5); + e[4] = fraction(25, 32); + e[5] = fraction( 1, 16); + e[6] = fraction(-1, 10); + } + + /** {@inheritDoc} */ + public T[] getC() { + final T[] c = MathArrays.buildArray(getField(), 6); + c[0] = fraction(2, 9); + c[1] = fraction(1, 3); + c[2] = fraction(1, 2); + c[3] = fraction(3, 5); + c[4] = getField().getOne(); + c[5] = getField().getOne(); + return c; + } + + /** {@inheritDoc} */ + public T[][] getA() { + final T[][] a = MathArrays.buildArray(getField(), 6, -1); + for (int i = 0; i < a.length; ++i) { + a[i] = MathArrays.buildArray(getField(), i + 1); + } + a[0][0] = fraction( 2, 9); + a[1][0] = fraction( 1, 12); + a[1][1] = fraction( 1, 4); + a[2][0] = fraction( 1, 8); + a[2][1] = getField().getZero(); + a[2][2] = fraction( 3, 8); + a[3][0] = fraction( 91, 500); + a[3][1] = fraction( -27, 100); + a[3][2] = fraction( 78, 125); + a[3][3] = fraction( 8, 125); + a[4][0] = fraction( -11, 20); + a[4][1] = fraction( 27, 20); + a[4][2] = fraction( 12, 5); + a[4][3] = fraction( -36, 5); + a[4][4] = fraction( 5, 1); + a[5][0] = fraction( 1, 12); + a[5][1] = getField().getZero(); + a[5][2] = fraction( 27, 32); + a[5][3] = fraction( -4, 3); + a[5][4] = fraction( 125, 96); + a[5][5] = fraction( 5, 48); + return a; + } + + /** {@inheritDoc} */ + public T[] getB() { + final T[] b = MathArrays.buildArray(getField(), 7); + b[0] = fraction( 1, 12); + b[1] = getField().getZero(); + b[2] = fraction( 27, 32); + b[3] = fraction( -4, 3); + b[4] = fraction(125, 96); + b[5] = fraction( 5, 48); + b[6] = getField().getZero(); + return b; + } + + /** {@inheritDoc} */ + @Override + protected HighamHall54FieldStepInterpolator<T> + createInterpolator(final boolean forward, T[][] yDotK, + final FieldODEStateAndDerivative<T> globalPreviousState, + final FieldODEStateAndDerivative<T> globalCurrentState, final FieldEquationsMapper<T> mapper) { + return new HighamHall54FieldStepInterpolator<T>(getField(), forward, yDotK, + globalPreviousState, globalCurrentState, + globalPreviousState, globalCurrentState, + mapper); + } + + /** {@inheritDoc} */ + @Override + public int getOrder() { + return 5; + } + + /** {@inheritDoc} */ + @Override + protected T estimateError(final T[][] yDotK, final T[] y0, final T[] y1, final T h) { + + T error = getField().getZero(); + + for (int j = 0; j < mainSetDimension; ++j) { + T errSum = yDotK[0][j].multiply(e[0]); + for (int l = 1; l < e.length; ++l) { + errSum = errSum.add(yDotK[l][j].multiply(e[l])); + } + + final T yScale = MathUtils.max(y0[j].abs(), y1[j].abs()); + final T tol = (vecAbsoluteTolerance == null) ? + yScale.multiply(scalRelativeTolerance).add(scalAbsoluteTolerance) : + yScale.multiply(vecRelativeTolerance[j]).add(vecAbsoluteTolerance[j]); + final T ratio = h.multiply(errSum).divide(tol); + error = error.add(ratio.multiply(ratio)); + + } + + return error.divide(mainSetDimension).sqrt(); + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/HighamHall54FieldStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/HighamHall54FieldStepInterpolator.java new file mode 100644 index 0000000..10240fd --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/HighamHall54FieldStepInterpolator.java @@ -0,0 +1,116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.Field; +import org.apache.commons.math3.RealFieldElement; +import org.apache.commons.math3.ode.FieldEquationsMapper; +import org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** + * This class represents an interpolator over the last step during an + * ODE integration for the 5(4) Higham and Hall integrator. + * + * @see HighamHall54FieldIntegrator + * + * @param <T> the type of the field elements + * @since 3.6 + */ + +class HighamHall54FieldStepInterpolator<T extends RealFieldElement<T>> + extends RungeKuttaFieldStepInterpolator<T> { + + /** Simple constructor. + * @param field field to which the time and state vector elements belong + * @param forward integration direction indicator + * @param yDotK slopes at the intermediate points + * @param globalPreviousState start of the global step + * @param globalCurrentState end of the global step + * @param softPreviousState start of the restricted step + * @param softCurrentState end of the restricted step + * @param mapper equations mapper for the all equations + */ + HighamHall54FieldStepInterpolator(final Field<T> field, final boolean forward, + final T[][] yDotK, + final FieldODEStateAndDerivative<T> globalPreviousState, + final FieldODEStateAndDerivative<T> globalCurrentState, + final FieldODEStateAndDerivative<T> softPreviousState, + final FieldODEStateAndDerivative<T> softCurrentState, + final FieldEquationsMapper<T> mapper) { + super(field, forward, yDotK, + globalPreviousState, globalCurrentState, softPreviousState, softCurrentState, + mapper); + } + + /** {@inheritDoc} */ + @Override + protected HighamHall54FieldStepInterpolator<T> create(final Field<T> newField, final boolean newForward, final T[][] newYDotK, + final FieldODEStateAndDerivative<T> newGlobalPreviousState, + final FieldODEStateAndDerivative<T> newGlobalCurrentState, + final FieldODEStateAndDerivative<T> newSoftPreviousState, + final FieldODEStateAndDerivative<T> newSoftCurrentState, + final FieldEquationsMapper<T> newMapper) { + return new HighamHall54FieldStepInterpolator<T>(newField, newForward, newYDotK, + newGlobalPreviousState, newGlobalCurrentState, + newSoftPreviousState, newSoftCurrentState, + newMapper); + } + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override + protected FieldODEStateAndDerivative<T> computeInterpolatedStateAndDerivatives(final FieldEquationsMapper<T> mapper, + final T time, final T theta, + final T thetaH, final T oneMinusThetaH) { + + final T bDot0 = theta.multiply(theta.multiply(theta.multiply( -10.0 ).add( 16.0 )).add(-15.0 / 2.0)).add(1); + final T bDot1 = time.getField().getZero(); + final T bDot2 = theta.multiply(theta.multiply(theta.multiply( 135.0 / 2.0).add(-729.0 / 8.0)).add(459.0 / 16.0)); + final T bDot3 = theta.multiply(theta.multiply(theta.multiply(-120.0 ).add( 152.0 )).add(-44.0 )); + final T bDot4 = theta.multiply(theta.multiply(theta.multiply( 125.0 / 2.0).add(-625.0 / 8.0)).add(375.0 / 16.0)); + final T bDot5 = theta.multiply( 5.0 / 8.0).multiply(theta.multiply(2).subtract(1)); + final T[] interpolatedState; + final T[] interpolatedDerivatives; + + if (getGlobalPreviousState() != null && theta.getReal() <= 0.5) { + final T b0 = thetaH.multiply(theta.multiply(theta.multiply(theta.multiply( -5.0 / 2.0).add( 16.0 / 3.0)).add(-15.0 / 4.0)).add(1)); + final T b1 = time.getField().getZero(); + final T b2 = thetaH.multiply(theta.multiply(theta.multiply(theta.multiply(135.0 / 8.0).add(-243.0 / 8.0)).add(459.0 / 32.0))); + final T b3 = thetaH.multiply(theta.multiply(theta.multiply(theta.multiply(-30.0 ).add( 152.0 / 3.0)).add(-22.0 ))); + final T b4 = thetaH.multiply(theta.multiply(theta.multiply(theta.multiply(125.0 / 8.0).add(-625.0 / 24.0)).add(375.0 / 32.0))); + final T b5 = thetaH.multiply(theta.multiply(theta.multiply( 5.0 / 12.0 ).add( -5.0 / 16.0))); + interpolatedState = previousStateLinearCombination(b0, b1, b2, b3, b4, b5); + interpolatedDerivatives = derivativeLinearCombination(bDot0, bDot1, bDot2, bDot3, bDot4, bDot5); + } else { + final T theta2 = theta.multiply(theta); + final T h = thetaH.divide(theta); + final T b0 = h.multiply( theta.multiply(theta.multiply(theta.multiply(theta.multiply(-5.0 / 2.0).add( 16.0 / 3.0)).add( -15.0 / 4.0)).add( 1.0 )).add( -1.0 / 12.0)); + final T b1 = time.getField().getZero(); + final T b2 = h.multiply(theta2.multiply(theta.multiply(theta.multiply( 135.0 / 8.0 ).add(-243.0 / 8.0)).add(459.0 / 32.0)).add( -27.0 / 32.0)); + final T b3 = h.multiply(theta2.multiply(theta.multiply(theta.multiply( -30.0 ).add( 152.0 / 3.0)).add(-22.0 )).add( 4.0 / 3.0)); + final T b4 = h.multiply(theta2.multiply(theta.multiply(theta.multiply( 125.0 / 8.0 ).add(-625.0 / 24.0)).add(375.0 / 32.0)).add(-125.0 / 96.0)); + final T b5 = h.multiply(theta2.multiply(theta.multiply( 5.0 / 12.0 ).add(-5.0 / 16.0)).add( -5.0 / 48.0)); + interpolatedState = currentStateLinearCombination(b0, b1, b2, b3, b4, b5); + interpolatedDerivatives = derivativeLinearCombination(bDot0, bDot1, bDot2, bDot3, bDot4, bDot5); + } + + return new FieldODEStateAndDerivative<T>(time, interpolatedState, interpolatedDerivatives); + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/HighamHall54Integrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/HighamHall54Integrator.java new file mode 100644 index 0000000..c48c4f9 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/HighamHall54Integrator.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.util.FastMath; + + +/** + * This class implements the 5(4) Higham and Hall integrator for + * Ordinary Differential Equations. + * + * <p>This integrator is an embedded Runge-Kutta integrator + * of order 5(4) used in local extrapolation mode (i.e. the solution + * is computed using the high order formula) with stepsize control + * (and automatic step initialization) and continuous output. This + * method uses 7 functions evaluations per step.</p> + * + * @since 1.2 + */ + +public class HighamHall54Integrator extends EmbeddedRungeKuttaIntegrator { + + /** Integrator method name. */ + private static final String METHOD_NAME = "Higham-Hall 5(4)"; + + /** Time steps Butcher array. */ + private static final double[] STATIC_C = { + 2.0/9.0, 1.0/3.0, 1.0/2.0, 3.0/5.0, 1.0, 1.0 + }; + + /** Internal weights Butcher array. */ + private static final double[][] STATIC_A = { + {2.0/9.0}, + {1.0/12.0, 1.0/4.0}, + {1.0/8.0, 0.0, 3.0/8.0}, + {91.0/500.0, -27.0/100.0, 78.0/125.0, 8.0/125.0}, + {-11.0/20.0, 27.0/20.0, 12.0/5.0, -36.0/5.0, 5.0}, + {1.0/12.0, 0.0, 27.0/32.0, -4.0/3.0, 125.0/96.0, 5.0/48.0} + }; + + /** Propagation weights Butcher array. */ + private static final double[] STATIC_B = { + 1.0/12.0, 0.0, 27.0/32.0, -4.0/3.0, 125.0/96.0, 5.0/48.0, 0.0 + }; + + /** Error weights Butcher array. */ + private static final double[] STATIC_E = { + -1.0/20.0, 0.0, 81.0/160.0, -6.0/5.0, 25.0/32.0, 1.0/16.0, -1.0/10.0 + }; + + /** Simple constructor. + * Build a fifth order Higham and Hall integrator with the given step bounds + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param scalAbsoluteTolerance allowed absolute error + * @param scalRelativeTolerance allowed relative error + */ + public HighamHall54Integrator(final double minStep, final double maxStep, + final double scalAbsoluteTolerance, + final double scalRelativeTolerance) { + super(METHOD_NAME, false, STATIC_C, STATIC_A, STATIC_B, new HighamHall54StepInterpolator(), + minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance); + } + + /** Simple constructor. + * Build a fifth order Higham and Hall integrator with the given step bounds + * @param minStep minimal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param maxStep maximal step (sign is irrelevant, regardless of + * integration direction, forward or backward), the last step can + * be smaller than this + * @param vecAbsoluteTolerance allowed absolute error + * @param vecRelativeTolerance allowed relative error + */ + public HighamHall54Integrator(final double minStep, final double maxStep, + final double[] vecAbsoluteTolerance, + final double[] vecRelativeTolerance) { + super(METHOD_NAME, false, STATIC_C, STATIC_A, STATIC_B, new HighamHall54StepInterpolator(), + minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance); + } + + /** {@inheritDoc} */ + @Override + public int getOrder() { + return 5; + } + + /** {@inheritDoc} */ + @Override + protected double estimateError(final double[][] yDotK, + final double[] y0, final double[] y1, + final double h) { + + double error = 0; + + for (int j = 0; j < mainSetDimension; ++j) { + double errSum = STATIC_E[0] * yDotK[0][j]; + for (int l = 1; l < STATIC_E.length; ++l) { + errSum += STATIC_E[l] * yDotK[l][j]; + } + + final double yScale = FastMath.max(FastMath.abs(y0[j]), FastMath.abs(y1[j])); + final double tol = (vecAbsoluteTolerance == null) ? + (scalAbsoluteTolerance + scalRelativeTolerance * yScale) : + (vecAbsoluteTolerance[j] + vecRelativeTolerance[j] * yScale); + final double ratio = h * errSum / tol; + error += ratio * ratio; + + } + + return FastMath.sqrt(error / mainSetDimension); + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/HighamHall54StepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/HighamHall54StepInterpolator.java new file mode 100644 index 0000000..682ec7c --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/HighamHall54StepInterpolator.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.ode.sampling.StepInterpolator; + +/** + * This class represents an interpolator over the last step during an + * ODE integration for the 5(4) Higham and Hall integrator. + * + * @see HighamHall54Integrator + * + * @since 1.2 + */ + +class HighamHall54StepInterpolator + extends RungeKuttaStepInterpolator { + + /** Serializable version identifier */ + private static final long serialVersionUID = 20111120L; + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link + * org.apache.commons.math3.ode.sampling.AbstractStepInterpolator#reinitialize} + * method should be called before using the instance in order to + * initialize the internal arrays. This constructor is used only + * in order to delay the initialization in some cases. The {@link + * EmbeddedRungeKuttaIntegrator} uses the prototyping design pattern + * to create the step interpolators by cloning an uninitialized model + * and later initializing the copy. + */ + // CHECKSTYLE: stop RedundantModifier + // the public modifier here is needed for serialization + public HighamHall54StepInterpolator() { + super(); + } + // CHECKSTYLE: resume RedundantModifier + + /** Copy constructor. + * @param interpolator interpolator to copy from. The copy is a deep + * copy: its arrays are separated from the original arrays of the + * instance + */ + HighamHall54StepInterpolator(final HighamHall54StepInterpolator interpolator) { + super(interpolator); + } + + /** {@inheritDoc} */ + @Override + protected StepInterpolator doCopy() { + return new HighamHall54StepInterpolator(this); + } + + + /** {@inheritDoc} */ + @Override + protected void computeInterpolatedStateAndDerivatives(final double theta, + final double oneMinusThetaH) { + + final double bDot0 = 1 + theta * (-15.0/2.0 + theta * (16.0 - 10.0 * theta)); + final double bDot2 = theta * (459.0/16.0 + theta * (-729.0/8.0 + 135.0/2.0 * theta)); + final double bDot3 = theta * (-44.0 + theta * (152.0 - 120.0 * theta)); + final double bDot4 = theta * (375.0/16.0 + theta * (-625.0/8.0 + 125.0/2.0 * theta)); + final double bDot5 = theta * 5.0/8.0 * (2 * theta - 1); + + if ((previousState != null) && (theta <= 0.5)) { + final double hTheta = h * theta; + final double b0 = hTheta * (1.0 + theta * (-15.0/4.0 + theta * (16.0/3.0 - 5.0/2.0 * theta))); + final double b2 = hTheta * ( theta * (459.0/32.0 + theta * (-243.0/8.0 + theta * 135.0/8.0))); + final double b3 = hTheta * ( theta * (-22.0 + theta * (152.0/3.0 + theta * -30.0))); + final double b4 = hTheta * ( theta * (375.0/32.0 + theta * (-625.0/24.0 + theta * 125.0/8.0))); + final double b5 = hTheta * ( theta * (-5.0/16.0 + theta * 5.0/12.0)); + for (int i = 0; i < interpolatedState.length; ++i) { + final double yDot0 = yDotK[0][i]; + final double yDot2 = yDotK[2][i]; + final double yDot3 = yDotK[3][i]; + final double yDot4 = yDotK[4][i]; + final double yDot5 = yDotK[5][i]; + interpolatedState[i] = + previousState[i] + b0 * yDot0 + b2 * yDot2 + b3 * yDot3 + b4 * yDot4 + b5 * yDot5; + interpolatedDerivatives[i] = + bDot0 * yDot0 + bDot2 * yDot2 + bDot3 * yDot3 + bDot4 * yDot4 + bDot5 * yDot5; + } + } else { + final double theta2 = theta * theta; + final double b0 = h * (-1.0/12.0 + theta * (1.0 + theta * (-15.0/4.0 + theta * (16.0/3.0 + theta * -5.0/2.0)))); + final double b2 = h * (-27.0/32.0 + theta2 * (459.0/32.0 + theta * (-243.0/8.0 + theta * 135.0/8.0))); + final double b3 = h * (4.0/3.0 + theta2 * (-22.0 + theta * (152.0/3.0 + theta * -30.0))); + final double b4 = h * (-125.0/96.0 + theta2 * (375.0/32.0 + theta * (-625.0/24.0 + theta * 125.0/8.0))); + final double b5 = h * (-5.0/48.0 + theta2 * (-5.0/16.0 + theta * 5.0/12.0)); + for (int i = 0; i < interpolatedState.length; ++i) { + final double yDot0 = yDotK[0][i]; + final double yDot2 = yDotK[2][i]; + final double yDot3 = yDotK[3][i]; + final double yDot4 = yDotK[4][i]; + final double yDot5 = yDotK[5][i]; + interpolatedState[i] = + currentState[i] + b0 * yDot0 + b2 * yDot2 + b3 * yDot3 + b4 * yDot4 + b5 * yDot5; + interpolatedDerivatives[i] = + bDot0 * yDot0 + bDot2 * yDot2 + bDot3 * yDot3 + bDot4 * yDot4 + bDot5 * yDot5; + } + } + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/LutherFieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/LutherFieldIntegrator.java new file mode 100644 index 0000000..7b57b5a --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/LutherFieldIntegrator.java @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.Field; +import org.apache.commons.math3.RealFieldElement; +import org.apache.commons.math3.ode.FieldEquationsMapper; +import org.apache.commons.math3.ode.FieldODEStateAndDerivative; +import org.apache.commons.math3.util.MathArrays; + + +/** + * This class implements the Luther sixth order Runge-Kutta + * integrator for Ordinary Differential Equations. + + * <p> + * This method is described in H. A. Luther 1968 paper <a + * href="http://www.ams.org/journals/mcom/1968-22-102/S0025-5718-68-99876-1/S0025-5718-68-99876-1.pdf"> + * An explicit Sixth-Order Runge-Kutta Formula</a>. + * </p> + + * <p>This method is an explicit Runge-Kutta method, its Butcher-array + * is the following one : + * <pre> + * 0 | 0 0 0 0 0 0 + * 1 | 1 0 0 0 0 0 + * 1/2 | 3/8 1/8 0 0 0 0 + * 2/3 | 8/27 2/27 8/27 0 0 0 + * (7-q)/14 | ( -21 + 9q)/392 ( -56 + 8q)/392 ( 336 - 48q)/392 ( -63 + 3q)/392 0 0 + * (7+q)/14 | (-1155 - 255q)/1960 ( -280 - 40q)/1960 ( 0 - 320q)/1960 ( 63 + 363q)/1960 ( 2352 + 392q)/1960 0 + * 1 | ( 330 + 105q)/180 ( 120 + 0q)/180 ( -200 + 280q)/180 ( 126 - 189q)/180 ( -686 - 126q)/180 ( 490 - 70q)/180 + * |-------------------------------------------------------------------------------------------------------------------------------------------------- + * | 1/20 0 16/45 0 49/180 49/180 1/20 + * </pre> + * where q = √21</p> + * + * @see EulerFieldIntegrator + * @see ClassicalRungeKuttaFieldIntegrator + * @see GillFieldIntegrator + * @see MidpointFieldIntegrator + * @see ThreeEighthesFieldIntegrator + * @param <T> the type of the field elements + * @since 3.6 + */ + +public class LutherFieldIntegrator<T extends RealFieldElement<T>> + extends RungeKuttaFieldIntegrator<T> { + + /** Simple constructor. + * Build a fourth-order Luther integrator with the given step. + * @param field field to which the time and state vector elements belong + * @param step integration step + */ + public LutherFieldIntegrator(final Field<T> field, final T step) { + super(field, "Luther", step); + } + + /** {@inheritDoc} */ + public T[] getC() { + final T q = getField().getZero().add(21).sqrt(); + final T[] c = MathArrays.buildArray(getField(), 6); + c[0] = getField().getOne(); + c[1] = fraction(1, 2); + c[2] = fraction(2, 3); + c[3] = q.subtract(7).divide(-14); + c[4] = q.add(7).divide(14); + c[5] = getField().getOne(); + return c; + } + + /** {@inheritDoc} */ + public T[][] getA() { + final T q = getField().getZero().add(21).sqrt(); + final T[][] a = MathArrays.buildArray(getField(), 6, -1); + for (int i = 0; i < a.length; ++i) { + a[i] = MathArrays.buildArray(getField(), i + 1); + } + a[0][0] = getField().getOne(); + a[1][0] = fraction(3, 8); + a[1][1] = fraction(1, 8); + a[2][0] = fraction(8, 27); + a[2][1] = fraction(2, 27); + a[2][2] = a[2][0]; + a[3][0] = q.multiply( 9).add( -21).divide( 392); + a[3][1] = q.multiply( 8).add( -56).divide( 392); + a[3][2] = q.multiply( -48).add( 336).divide( 392); + a[3][3] = q.multiply( 3).add( -63).divide( 392); + a[4][0] = q.multiply(-255).add(-1155).divide(1960); + a[4][1] = q.multiply( -40).add( -280).divide(1960); + a[4][2] = q.multiply(-320) .divide(1960); + a[4][3] = q.multiply( 363).add( 63).divide(1960); + a[4][4] = q.multiply( 392).add( 2352).divide(1960); + a[5][0] = q.multiply( 105).add( 330).divide( 180); + a[5][1] = fraction(2, 3); + a[5][2] = q.multiply( 280).add( -200).divide( 180); + a[5][3] = q.multiply(-189).add( 126).divide( 180); + a[5][4] = q.multiply(-126).add( -686).divide( 180); + a[5][5] = q.multiply( -70).add( 490).divide( 180); + return a; + } + + /** {@inheritDoc} */ + public T[] getB() { + + final T[] b = MathArrays.buildArray(getField(), 7); + b[0] = fraction( 1, 20); + b[1] = getField().getZero(); + b[2] = fraction(16, 45); + b[3] = getField().getZero(); + b[4] = fraction(49, 180); + b[5] = b[4]; + b[6] = b[0]; + + return b; + + } + + /** {@inheritDoc} */ + @Override + protected LutherFieldStepInterpolator<T> + createInterpolator(final boolean forward, T[][] yDotK, + final FieldODEStateAndDerivative<T> globalPreviousState, + final FieldODEStateAndDerivative<T> globalCurrentState, + final FieldEquationsMapper<T> mapper) { + return new LutherFieldStepInterpolator<T>(getField(), forward, yDotK, + globalPreviousState, globalCurrentState, + globalPreviousState, globalCurrentState, + mapper); + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/LutherFieldStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/LutherFieldStepInterpolator.java new file mode 100644 index 0000000..9e38a96 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/LutherFieldStepInterpolator.java @@ -0,0 +1,224 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.Field; +import org.apache.commons.math3.RealFieldElement; +import org.apache.commons.math3.ode.FieldEquationsMapper; +import org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** + * This class represents an interpolator over the last step during an + * ODE integration for the 6th order Luther integrator. + * + * <p>This interpolator computes dense output inside the last + * step computed. The interpolation equation is consistent with the + * integration scheme.</p> + * + * @see LutherFieldIntegrator + * @param <T> the type of the field elements + * @since 3.6 + */ + +class LutherFieldStepInterpolator<T extends RealFieldElement<T>> + extends RungeKuttaFieldStepInterpolator<T> { + + /** -49 - 49 q. */ + private final T c5a; + + /** 392 + 287 q. */ + private final T c5b; + + /** -637 - 357 q. */ + private final T c5c; + + /** 833 + 343 q. */ + private final T c5d; + + /** -49 + 49 q. */ + private final T c6a; + + /** -392 - 287 q. */ + private final T c6b; + + /** -637 + 357 q. */ + private final T c6c; + + /** 833 - 343 q. */ + private final T c6d; + + /** 49 + 49 q. */ + private final T d5a; + + /** -1372 - 847 q. */ + private final T d5b; + + /** 2254 + 1029 q */ + private final T d5c; + + /** 49 - 49 q. */ + private final T d6a; + + /** -1372 + 847 q. */ + private final T d6b; + + /** 2254 - 1029 q */ + private final T d6c; + + /** Simple constructor. + * @param field field to which the time and state vector elements belong + * @param forward integration direction indicator + * @param yDotK slopes at the intermediate points + * @param globalPreviousState start of the global step + * @param globalCurrentState end of the global step + * @param softPreviousState start of the restricted step + * @param softCurrentState end of the restricted step + * @param mapper equations mapper for the all equations + */ + LutherFieldStepInterpolator(final Field<T> field, final boolean forward, + final T[][] yDotK, + final FieldODEStateAndDerivative<T> globalPreviousState, + final FieldODEStateAndDerivative<T> globalCurrentState, + final FieldODEStateAndDerivative<T> softPreviousState, + final FieldODEStateAndDerivative<T> softCurrentState, + final FieldEquationsMapper<T> mapper) { + super(field, forward, yDotK, + globalPreviousState, globalCurrentState, softPreviousState, softCurrentState, + mapper); + final T q = field.getZero().add(21).sqrt(); + c5a = q.multiply( -49).add( -49); + c5b = q.multiply( 287).add( 392); + c5c = q.multiply( -357).add( -637); + c5d = q.multiply( 343).add( 833); + c6a = q.multiply( 49).add( -49); + c6b = q.multiply( -287).add( 392); + c6c = q.multiply( 357).add( -637); + c6d = q.multiply( -343).add( 833); + d5a = q.multiply( 49).add( 49); + d5b = q.multiply( -847).add(-1372); + d5c = q.multiply( 1029).add( 2254); + d6a = q.multiply( -49).add( 49); + d6b = q.multiply( 847).add(-1372); + d6c = q.multiply(-1029).add( 2254); + } + + /** {@inheritDoc} */ + @Override + protected LutherFieldStepInterpolator<T> create(final Field<T> newField, final boolean newForward, final T[][] newYDotK, + final FieldODEStateAndDerivative<T> newGlobalPreviousState, + final FieldODEStateAndDerivative<T> newGlobalCurrentState, + final FieldODEStateAndDerivative<T> newSoftPreviousState, + final FieldODEStateAndDerivative<T> newSoftCurrentState, + final FieldEquationsMapper<T> newMapper) { + return new LutherFieldStepInterpolator<T>(newField, newForward, newYDotK, + newGlobalPreviousState, newGlobalCurrentState, + newSoftPreviousState, newSoftCurrentState, + newMapper); + } + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override + protected FieldODEStateAndDerivative<T> computeInterpolatedStateAndDerivatives(final FieldEquationsMapper<T> mapper, + final T time, final T theta, + final T thetaH, final T oneMinusThetaH) { + + // the coefficients below have been computed by solving the + // order conditions from a theorem from Butcher (1963), using + // the method explained in Folkmar Bornemann paper "Runge-Kutta + // Methods, Trees, and Maple", Center of Mathematical Sciences, Munich + // University of Technology, February 9, 2001 + //<http://wwwzenger.informatik.tu-muenchen.de/selcuk/sjam012101.html> + + // the method is implemented in the rkcheck tool + // <https://www.spaceroots.org/software/rkcheck/index.html>. + // Running it for order 5 gives the following order conditions + // for an interpolator: + // order 1 conditions + // \sum_{i=1}^{i=s}\left(b_{i} \right) =1 + // order 2 conditions + // \sum_{i=1}^{i=s}\left(b_{i} c_{i}\right) = \frac{\theta}{2} + // order 3 conditions + // \sum_{i=2}^{i=s}\left(b_{i} \sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j} \right)}\right) = \frac{\theta^{2}}{6} + // \sum_{i=1}^{i=s}\left(b_{i} c_{i}^{2}\right) = \frac{\theta^{2}}{3} + // order 4 conditions + // \sum_{i=3}^{i=s}\left(b_{i} \sum_{j=2}^{j=i-1}{\left(a_{i,j} \sum_{k=1}^{k=j-1}{\left(a_{j,k} c_{k} \right)} \right)}\right) = \frac{\theta^{3}}{24} + // \sum_{i=2}^{i=s}\left(b_{i} \sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j}^{2} \right)}\right) = \frac{\theta^{3}}{12} + // \sum_{i=2}^{i=s}\left(b_{i} c_{i}\sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j} \right)}\right) = \frac{\theta^{3}}{8} + // \sum_{i=1}^{i=s}\left(b_{i} c_{i}^{3}\right) = \frac{\theta^{3}}{4} + // order 5 conditions + // \sum_{i=4}^{i=s}\left(b_{i} \sum_{j=3}^{j=i-1}{\left(a_{i,j} \sum_{k=2}^{k=j-1}{\left(a_{j,k} \sum_{l=1}^{l=k-1}{\left(a_{k,l} c_{l} \right)} \right)} \right)}\right) = \frac{\theta^{4}}{120} + // \sum_{i=3}^{i=s}\left(b_{i} \sum_{j=2}^{j=i-1}{\left(a_{i,j} \sum_{k=1}^{k=j-1}{\left(a_{j,k} c_{k}^{2} \right)} \right)}\right) = \frac{\theta^{4}}{60} + // \sum_{i=3}^{i=s}\left(b_{i} \sum_{j=2}^{j=i-1}{\left(a_{i,j} c_{j}\sum_{k=1}^{k=j-1}{\left(a_{j,k} c_{k} \right)} \right)}\right) = \frac{\theta^{4}}{40} + // \sum_{i=2}^{i=s}\left(b_{i} \sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j}^{3} \right)}\right) = \frac{\theta^{4}}{20} + // \sum_{i=3}^{i=s}\left(b_{i} c_{i}\sum_{j=2}^{j=i-1}{\left(a_{i,j} \sum_{k=1}^{k=j-1}{\left(a_{j,k} c_{k} \right)} \right)}\right) = \frac{\theta^{4}}{30} + // \sum_{i=2}^{i=s}\left(b_{i} c_{i}\sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j}^{2} \right)}\right) = \frac{\theta^{4}}{15} + // \sum_{i=2}^{i=s}\left(b_{i} \left(\sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j} \right)} \right)^{2}\right) = \frac{\theta^{4}}{20} + // \sum_{i=2}^{i=s}\left(b_{i} c_{i}^{2}\sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j} \right)}\right) = \frac{\theta^{4}}{10} + // \sum_{i=1}^{i=s}\left(b_{i} c_{i}^{4}\right) = \frac{\theta^{4}}{5} + + // The a_{j,k} and c_{k} are given by the integrator Butcher arrays. What remains to solve + // are the b_i for the interpolator. They are found by solving the above equations. + // For a given interpolator, some equations are redundant, so in our case when we select + // all equations from order 1 to 4, we still don't have enough independent equations + // to solve from b_1 to b_7. We need to also select one equation from order 5. Here, + // we selected the last equation. It appears this choice implied at least the last 3 equations + // are fulfilled, but some of the former ones are not, so the resulting interpolator is order 5. + // At the end, we get the b_i as polynomials in theta. + + final T coeffDot1 = theta.multiply(theta.multiply(theta.multiply(theta.multiply( 21 ).add( -47 )).add( 36 )).add( -54 / 5.0)).add(1); + final T coeffDot2 = time.getField().getZero(); + final T coeffDot3 = theta.multiply(theta.multiply(theta.multiply(theta.multiply( 112 ).add(-608 / 3.0)).add( 320 / 3.0 )).add(-208 / 15.0)); + final T coeffDot4 = theta.multiply(theta.multiply(theta.multiply(theta.multiply( -567 / 5.0).add( 972 / 5.0)).add( -486 / 5.0 )).add( 324 / 25.0)); + final T coeffDot5 = theta.multiply(theta.multiply(theta.multiply(theta.multiply(c5a.divide(5)).add(c5b.divide(15))).add(c5c.divide(30))).add(c5d.divide(150))); + final T coeffDot6 = theta.multiply(theta.multiply(theta.multiply(theta.multiply(c6a.divide(5)).add(c6b.divide(15))).add(c6c.divide(30))).add(c6d.divide(150))); + final T coeffDot7 = theta.multiply(theta.multiply(theta.multiply( 3.0 ).add( -3 )).add( 3 / 5.0)); + final T[] interpolatedState; + final T[] interpolatedDerivatives; + + if (getGlobalPreviousState() != null && theta.getReal() <= 0.5) { + + final T s = thetaH; + final T coeff1 = s.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply( 21 / 5.0).add( -47 / 4.0)).add( 12 )).add( -27 / 5.0)).add(1)); + final T coeff2 = time.getField().getZero(); + final T coeff3 = s.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply( 112 / 5.0).add(-152 / 3.0)).add( 320 / 9.0 )).add(-104 / 15.0))); + final T coeff4 = s.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply(-567 / 25.0).add( 243 / 5.0)).add( -162 / 5.0 )).add( 162 / 25.0))); + final T coeff5 = s.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply(c5a.divide(25)).add(c5b.divide(60))).add(c5c.divide(90))).add(c5d.divide(300)))); + final T coeff6 = s.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply(c6a.divide(25)).add(c6b.divide(60))).add(c6c.divide(90))).add(c6d.divide(300)))); + final T coeff7 = s.multiply(theta.multiply(theta.multiply(theta.multiply( 3 / 4.0 ).add( -1 )).add( 3 / 10.0))); + interpolatedState = previousStateLinearCombination(coeff1, coeff2, coeff3, coeff4, coeff5, coeff6, coeff7); + interpolatedDerivatives = derivativeLinearCombination(coeffDot1, coeffDot2, coeffDot3, coeffDot4, coeffDot5, coeffDot6, coeffDot7); + } else { + + final T s = oneMinusThetaH; + final T coeff1 = s.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply( -21 / 5.0).add( 151 / 20.0)).add( -89 / 20.0)).add( 19 / 20.0)).add(- 1 / 20.0)); + final T coeff2 = time.getField().getZero(); + final T coeff3 = s.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply(-112 / 5.0).add( 424 / 15.0)).add( -328 / 45.0)).add( -16 / 45.0)).add(-16 / 45.0)); + final T coeff4 = s.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply( 567 / 25.0).add( -648 / 25.0)).add( 162 / 25.0)))); + final T coeff5 = s.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply(d5a.divide(25)).add(d5b.divide(300))).add(d5c.divide(900))).add( -49 / 180.0)).add(-49 / 180.0)); + final T coeff6 = s.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply(d6a.divide(25)).add(d6b.divide(300))).add(d6c.divide(900))).add( -49 / 180.0)).add(-49 / 180.0)); + final T coeff7 = s.multiply( theta.multiply(theta.multiply(theta.multiply( -3 / 4.0 ).add( 1 / 4.0)).add( -1 / 20.0)).add( -1 / 20.0)); + interpolatedState = currentStateLinearCombination(coeff1, coeff2, coeff3, coeff4, coeff5, coeff6, coeff7); + interpolatedDerivatives = derivativeLinearCombination(coeffDot1, coeffDot2, coeffDot3, coeffDot4, coeffDot5, coeffDot6, coeffDot7); + } + + return new FieldODEStateAndDerivative<T>(time, interpolatedState, interpolatedDerivatives); + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/LutherIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/LutherIntegrator.java new file mode 100644 index 0000000..d3d0d6a --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/LutherIntegrator.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.util.FastMath; + + +/** + * This class implements the Luther sixth order Runge-Kutta + * integrator for Ordinary Differential Equations. + + * <p> + * This method is described in H. A. Luther 1968 paper <a + * href="http://www.ams.org/journals/mcom/1968-22-102/S0025-5718-68-99876-1/S0025-5718-68-99876-1.pdf"> + * An explicit Sixth-Order Runge-Kutta Formula</a>. + * </p> + + * <p>This method is an explicit Runge-Kutta method, its Butcher-array + * is the following one : + * <pre> + * 0 | 0 0 0 0 0 0 + * 1 | 1 0 0 0 0 0 + * 1/2 | 3/8 1/8 0 0 0 0 + * 2/3 | 8/27 2/27 8/27 0 0 0 + * (7-q)/14 | ( -21 + 9q)/392 ( -56 + 8q)/392 ( 336 - 48q)/392 ( -63 + 3q)/392 0 0 + * (7+q)/14 | (-1155 - 255q)/1960 ( -280 - 40q)/1960 ( 0 - 320q)/1960 ( 63 + 363q)/1960 ( 2352 + 392q)/1960 0 + * 1 | ( 330 + 105q)/180 ( 120 + 0q)/180 ( -200 + 280q)/180 ( 126 - 189q)/180 ( -686 - 126q)/180 ( 490 - 70q)/180 + * |-------------------------------------------------------------------------------------------------------------------------------------------------- + * | 1/20 0 16/45 0 49/180 49/180 1/20 + * </pre> + * where q = √21</p> + * + * @see EulerIntegrator + * @see ClassicalRungeKuttaIntegrator + * @see GillIntegrator + * @see MidpointIntegrator + * @see ThreeEighthesIntegrator + * @since 3.3 + */ + +public class LutherIntegrator extends RungeKuttaIntegrator { + + /** Square root. */ + private static final double Q = FastMath.sqrt(21); + + /** Time steps Butcher array. */ + private static final double[] STATIC_C = { + 1.0, 1.0 / 2.0, 2.0 / 3.0, (7.0 - Q) / 14.0, (7.0 + Q) / 14.0, 1.0 + }; + + /** Internal weights Butcher array. */ + private static final double[][] STATIC_A = { + { 1.0 }, + { 3.0 / 8.0, 1.0 / 8.0 }, + { 8.0 / 27.0, 2.0 / 27.0, 8.0 / 27.0 }, + { ( -21.0 + 9.0 * Q) / 392.0, ( -56.0 + 8.0 * Q) / 392.0, ( 336.0 - 48.0 * Q) / 392.0, (-63.0 + 3.0 * Q) / 392.0 }, + { (-1155.0 - 255.0 * Q) / 1960.0, (-280.0 - 40.0 * Q) / 1960.0, ( 0.0 - 320.0 * Q) / 1960.0, ( 63.0 + 363.0 * Q) / 1960.0, (2352.0 + 392.0 * Q) / 1960.0 }, + { ( 330.0 + 105.0 * Q) / 180.0, ( 120.0 + 0.0 * Q) / 180.0, (-200.0 + 280.0 * Q) / 180.0, (126.0 - 189.0 * Q) / 180.0, (-686.0 - 126.0 * Q) / 180.0, (490.0 - 70.0 * Q) / 180.0 } + }; + + /** Propagation weights Butcher array. */ + private static final double[] STATIC_B = { + 1.0 / 20.0, 0, 16.0 / 45.0, 0, 49.0 / 180.0, 49.0 / 180.0, 1.0 / 20.0 + }; + + /** Simple constructor. + * Build a fourth-order Luther integrator with the given step. + * @param step integration step + */ + public LutherIntegrator(final double step) { + super("Luther", STATIC_C, STATIC_A, STATIC_B, new LutherStepInterpolator(), step); + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/LutherStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/LutherStepInterpolator.java new file mode 100644 index 0000000..207a9ea --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/LutherStepInterpolator.java @@ -0,0 +1,182 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.ode.sampling.StepInterpolator; +import org.apache.commons.math3.util.FastMath; + +/** + * This class represents an interpolator over the last step during an + * ODE integration for the 6th order Luther integrator. + * + * <p>This interpolator computes dense output inside the last + * step computed. The interpolation equation is consistent with the + * integration scheme.</p> + * + * @see LutherIntegrator + * @since 3.3 + */ + +class LutherStepInterpolator extends RungeKuttaStepInterpolator { + + /** Serializable version identifier */ + private static final long serialVersionUID = 20140416L; + + /** Square root. */ + private static final double Q = FastMath.sqrt(21); + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link + * org.apache.commons.math3.ode.sampling.AbstractStepInterpolator#reinitialize} + * method should be called before using the instance in order to + * initialize the internal arrays. This constructor is used only + * in order to delay the initialization in some cases. The {@link + * RungeKuttaIntegrator} class uses the prototyping design pattern + * to create the step interpolators by cloning an uninitialized model + * and later initializing the copy. + */ + // CHECKSTYLE: stop RedundantModifier + // the public modifier here is needed for serialization + public LutherStepInterpolator() { + } + // CHECKSTYLE: resume RedundantModifier + + /** Copy constructor. + * @param interpolator interpolator to copy from. The copy is a deep + * copy: its arrays are separated from the original arrays of the + * instance + */ + LutherStepInterpolator(final LutherStepInterpolator interpolator) { + super(interpolator); + } + + /** {@inheritDoc} */ + @Override + protected StepInterpolator doCopy() { + return new LutherStepInterpolator(this); + } + + + /** {@inheritDoc} */ + @Override + protected void computeInterpolatedStateAndDerivatives(final double theta, + final double oneMinusThetaH) { + + // the coefficients below have been computed by solving the + // order conditions from a theorem from Butcher (1963), using + // the method explained in Folkmar Bornemann paper "Runge-Kutta + // Methods, Trees, and Maple", Center of Mathematical Sciences, Munich + // University of Technology, February 9, 2001 + //<http://wwwzenger.informatik.tu-muenchen.de/selcuk/sjam012101.html> + + // the method is implemented in the rkcheck tool + // <https://www.spaceroots.org/software/rkcheck/index.html>. + // Running it for order 5 gives the following order conditions + // for an interpolator: + // order 1 conditions + // \sum_{i=1}^{i=s}\left(b_{i} \right) =1 + // order 2 conditions + // \sum_{i=1}^{i=s}\left(b_{i} c_{i}\right) = \frac{\theta}{2} + // order 3 conditions + // \sum_{i=2}^{i=s}\left(b_{i} \sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j} \right)}\right) = \frac{\theta^{2}}{6} + // \sum_{i=1}^{i=s}\left(b_{i} c_{i}^{2}\right) = \frac{\theta^{2}}{3} + // order 4 conditions + // \sum_{i=3}^{i=s}\left(b_{i} \sum_{j=2}^{j=i-1}{\left(a_{i,j} \sum_{k=1}^{k=j-1}{\left(a_{j,k} c_{k} \right)} \right)}\right) = \frac{\theta^{3}}{24} + // \sum_{i=2}^{i=s}\left(b_{i} \sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j}^{2} \right)}\right) = \frac{\theta^{3}}{12} + // \sum_{i=2}^{i=s}\left(b_{i} c_{i}\sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j} \right)}\right) = \frac{\theta^{3}}{8} + // \sum_{i=1}^{i=s}\left(b_{i} c_{i}^{3}\right) = \frac{\theta^{3}}{4} + // order 5 conditions + // \sum_{i=4}^{i=s}\left(b_{i} \sum_{j=3}^{j=i-1}{\left(a_{i,j} \sum_{k=2}^{k=j-1}{\left(a_{j,k} \sum_{l=1}^{l=k-1}{\left(a_{k,l} c_{l} \right)} \right)} \right)}\right) = \frac{\theta^{4}}{120} + // \sum_{i=3}^{i=s}\left(b_{i} \sum_{j=2}^{j=i-1}{\left(a_{i,j} \sum_{k=1}^{k=j-1}{\left(a_{j,k} c_{k}^{2} \right)} \right)}\right) = \frac{\theta^{4}}{60} + // \sum_{i=3}^{i=s}\left(b_{i} \sum_{j=2}^{j=i-1}{\left(a_{i,j} c_{j}\sum_{k=1}^{k=j-1}{\left(a_{j,k} c_{k} \right)} \right)}\right) = \frac{\theta^{4}}{40} + // \sum_{i=2}^{i=s}\left(b_{i} \sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j}^{3} \right)}\right) = \frac{\theta^{4}}{20} + // \sum_{i=3}^{i=s}\left(b_{i} c_{i}\sum_{j=2}^{j=i-1}{\left(a_{i,j} \sum_{k=1}^{k=j-1}{\left(a_{j,k} c_{k} \right)} \right)}\right) = \frac{\theta^{4}}{30} + // \sum_{i=2}^{i=s}\left(b_{i} c_{i}\sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j}^{2} \right)}\right) = \frac{\theta^{4}}{15} + // \sum_{i=2}^{i=s}\left(b_{i} \left(\sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j} \right)} \right)^{2}\right) = \frac{\theta^{4}}{20} + // \sum_{i=2}^{i=s}\left(b_{i} c_{i}^{2}\sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j} \right)}\right) = \frac{\theta^{4}}{10} + // \sum_{i=1}^{i=s}\left(b_{i} c_{i}^{4}\right) = \frac{\theta^{4}}{5} + + // The a_{j,k} and c_{k} are given by the integrator Butcher arrays. What remains to solve + // are the b_i for the interpolator. They are found by solving the above equations. + // For a given interpolator, some equations are redundant, so in our case when we select + // all equations from order 1 to 4, we still don't have enough independent equations + // to solve from b_1 to b_7. We need to also select one equation from order 5. Here, + // we selected the last equation. It appears this choice implied at least the last 3 equations + // are fulfilled, but some of the former ones are not, so the resulting interpolator is order 5. + // At the end, we get the b_i as polynomials in theta. + + final double coeffDot1 = 1 + theta * ( -54 / 5.0 + theta * ( 36 + theta * ( -47 + theta * 21))); + final double coeffDot2 = 0; + final double coeffDot3 = theta * (-208 / 15.0 + theta * ( 320 / 3.0 + theta * (-608 / 3.0 + theta * 112))); + final double coeffDot4 = theta * ( 324 / 25.0 + theta * ( -486 / 5.0 + theta * ( 972 / 5.0 + theta * -567 / 5.0))); + final double coeffDot5 = theta * ((833 + 343 * Q) / 150.0 + theta * ((-637 - 357 * Q) / 30.0 + theta * ((392 + 287 * Q) / 15.0 + theta * (-49 - 49 * Q) / 5.0))); + final double coeffDot6 = theta * ((833 - 343 * Q) / 150.0 + theta * ((-637 + 357 * Q) / 30.0 + theta * ((392 - 287 * Q) / 15.0 + theta * (-49 + 49 * Q) / 5.0))); + final double coeffDot7 = theta * ( 3 / 5.0 + theta * ( -3 + theta * 3)); + + if ((previousState != null) && (theta <= 0.5)) { + + final double coeff1 = 1 + theta * ( -27 / 5.0 + theta * ( 12 + theta * ( -47 / 4.0 + theta * 21 / 5.0))); + final double coeff2 = 0; + final double coeff3 = theta * (-104 / 15.0 + theta * ( 320 / 9.0 + theta * (-152 / 3.0 + theta * 112 / 5.0))); + final double coeff4 = theta * ( 162 / 25.0 + theta * ( -162 / 5.0 + theta * ( 243 / 5.0 + theta * -567 / 25.0))); + final double coeff5 = theta * ((833 + 343 * Q) / 300.0 + theta * ((-637 - 357 * Q) / 90.0 + theta * ((392 + 287 * Q) / 60.0 + theta * (-49 - 49 * Q) / 25.0))); + final double coeff6 = theta * ((833 - 343 * Q) / 300.0 + theta * ((-637 + 357 * Q) / 90.0 + theta * ((392 - 287 * Q) / 60.0 + theta * (-49 + 49 * Q) / 25.0))); + final double coeff7 = theta * ( 3 / 10.0 + theta * ( -1 + theta * ( 3 / 4.0))); + for (int i = 0; i < interpolatedState.length; ++i) { + final double yDot1 = yDotK[0][i]; + final double yDot2 = yDotK[1][i]; + final double yDot3 = yDotK[2][i]; + final double yDot4 = yDotK[3][i]; + final double yDot5 = yDotK[4][i]; + final double yDot6 = yDotK[5][i]; + final double yDot7 = yDotK[6][i]; + interpolatedState[i] = previousState[i] + + theta * h * (coeff1 * yDot1 + coeff2 * yDot2 + coeff3 * yDot3 + + coeff4 * yDot4 + coeff5 * yDot5 + coeff6 * yDot6 + coeff7 * yDot7); + interpolatedDerivatives[i] = coeffDot1 * yDot1 + coeffDot2 * yDot2 + coeffDot3 * yDot3 + + coeffDot4 * yDot4 + coeffDot5 * yDot5 + coeffDot6 * yDot6 + coeffDot7 * yDot7; + } + } else { + + final double coeff1 = -1 / 20.0 + theta * ( 19 / 20.0 + theta * ( -89 / 20.0 + theta * ( 151 / 20.0 + theta * -21 / 5.0))); + final double coeff2 = 0; + final double coeff3 = -16 / 45.0 + theta * ( -16 / 45.0 + theta * ( -328 / 45.0 + theta * ( 424 / 15.0 + theta * -112 / 5.0))); + final double coeff4 = theta * ( theta * ( 162 / 25.0 + theta * ( -648 / 25.0 + theta * 567 / 25.0))); + final double coeff5 = -49 / 180.0 + theta * ( -49 / 180.0 + theta * ((2254 + 1029 * Q) / 900.0 + theta * ((-1372 - 847 * Q) / 300.0 + theta * ( 49 + 49 * Q) / 25.0))); + final double coeff6 = -49 / 180.0 + theta * ( -49 / 180.0 + theta * ((2254 - 1029 * Q) / 900.0 + theta * ((-1372 + 847 * Q) / 300.0 + theta * ( 49 - 49 * Q) / 25.0))); + final double coeff7 = -1 / 20.0 + theta * ( -1 / 20.0 + theta * ( 1 / 4.0 + theta * ( -3 / 4.0))); + for (int i = 0; i < interpolatedState.length; ++i) { + final double yDot1 = yDotK[0][i]; + final double yDot2 = yDotK[1][i]; + final double yDot3 = yDotK[2][i]; + final double yDot4 = yDotK[3][i]; + final double yDot5 = yDotK[4][i]; + final double yDot6 = yDotK[5][i]; + final double yDot7 = yDotK[6][i]; + interpolatedState[i] = currentState[i] + + oneMinusThetaH * (coeff1 * yDot1 + coeff2 * yDot2 + coeff3 * yDot3 + + coeff4 * yDot4 + coeff5 * yDot5 + coeff6 * yDot6 + coeff7 * yDot7); + interpolatedDerivatives[i] = coeffDot1 * yDot1 + coeffDot2 * yDot2 + coeffDot3 * yDot3 + + coeffDot4 * yDot4 + coeffDot5 * yDot5 + coeffDot6 * yDot6 + coeffDot7 * yDot7; + } + } + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/MidpointFieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/MidpointFieldIntegrator.java new file mode 100644 index 0000000..e0dd8cf --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/MidpointFieldIntegrator.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.Field; +import org.apache.commons.math3.RealFieldElement; +import org.apache.commons.math3.ode.FieldEquationsMapper; +import org.apache.commons.math3.ode.FieldODEStateAndDerivative; +import org.apache.commons.math3.util.MathArrays; + +/** + * This class implements a second order Runge-Kutta integrator for + * Ordinary Differential Equations. + * + * <p>This method is an explicit Runge-Kutta method, its Butcher-array + * is the following one : + * <pre> + * 0 | 0 0 + * 1/2 | 1/2 0 + * |---------- + * | 0 1 + * </pre> + * </p> + * + * @see EulerFieldIntegrator + * @see ClassicalRungeKuttaFieldIntegrator + * @see GillFieldIntegrator + * @see ThreeEighthesFieldIntegrator + * @see LutherFieldIntegrator + * + * @param <T> the type of the field elements + * @since 3.6 + */ + +public class MidpointFieldIntegrator<T extends RealFieldElement<T>> extends RungeKuttaFieldIntegrator<T> { + + /** Simple constructor. + * Build a midpoint integrator with the given step. + * @param field field to which the time and state vector elements belong + * @param step integration step + */ + public MidpointFieldIntegrator(final Field<T> field, final T step) { + super(field, "midpoint", step); + } + + /** {@inheritDoc} */ + public T[] getC() { + final T[] c = MathArrays.buildArray(getField(), 1); + c[0] = getField().getOne().multiply(0.5); + return c; + } + + /** {@inheritDoc} */ + public T[][] getA() { + final T[][] a = MathArrays.buildArray(getField(), 1, 1); + a[0][0] = fraction(1, 2); + return a; + } + + /** {@inheritDoc} */ + public T[] getB() { + final T[] b = MathArrays.buildArray(getField(), 2); + b[0] = getField().getZero(); + b[1] = getField().getOne(); + return b; + } + + /** {@inheritDoc} */ + @Override + protected MidpointFieldStepInterpolator<T> + createInterpolator(final boolean forward, T[][] yDotK, + final FieldODEStateAndDerivative<T> globalPreviousState, + final FieldODEStateAndDerivative<T> globalCurrentState, + final FieldEquationsMapper<T> mapper) { + return new MidpointFieldStepInterpolator<T>(getField(), forward, yDotK, + globalPreviousState, globalCurrentState, + globalPreviousState, globalCurrentState, + mapper); + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/MidpointFieldStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/MidpointFieldStepInterpolator.java new file mode 100644 index 0000000..911e3b2 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/MidpointFieldStepInterpolator.java @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.Field; +import org.apache.commons.math3.RealFieldElement; +import org.apache.commons.math3.ode.FieldEquationsMapper; +import org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** + * This class implements a step interpolator for second order + * Runge-Kutta integrator. + * + * <p>This interpolator computes dense output inside the last + * step computed. The interpolation equation is consistent with the + * integration scheme : + * <ul> + * <li>Using reference point at step start:<br> + * y(t<sub>n</sub> + θ h) = y (t<sub>n</sub>) + θ h [(1 - θ) y'<sub>1</sub> + θ y'<sub>2</sub>] + * </li> + * <li>Using reference point at step end:<br> + * y(t<sub>n</sub> + θ h) = y (t<sub>n</sub> + h) + (1-θ) h [θ y'<sub>1</sub> - (1+θ) y'<sub>2</sub>] + * </li> + * </ul> + * </p> + * + * where θ belongs to [0 ; 1] and where y'<sub>1</sub> and y'<sub>2</sub> are the two + * evaluations of the derivatives already computed during the + * step.</p> + * + * @see MidpointFieldIntegrator + * @param <T> the type of the field elements + * @since 3.6 + */ + +class MidpointFieldStepInterpolator<T extends RealFieldElement<T>> + extends RungeKuttaFieldStepInterpolator<T> { + + /** Simple constructor. + * @param field field to which the time and state vector elements belong + * @param forward integration direction indicator + * @param yDotK slopes at the intermediate points + * @param globalPreviousState start of the global step + * @param globalCurrentState end of the global step + * @param softPreviousState start of the restricted step + * @param softCurrentState end of the restricted step + * @param mapper equations mapper for the all equations + */ + MidpointFieldStepInterpolator(final Field<T> field, final boolean forward, + final T[][] yDotK, + final FieldODEStateAndDerivative<T> globalPreviousState, + final FieldODEStateAndDerivative<T> globalCurrentState, + final FieldODEStateAndDerivative<T> softPreviousState, + final FieldODEStateAndDerivative<T> softCurrentState, + final FieldEquationsMapper<T> mapper) { + super(field, forward, yDotK, + globalPreviousState, globalCurrentState, softPreviousState, softCurrentState, + mapper); + } + + /** {@inheritDoc} */ + @Override + protected MidpointFieldStepInterpolator<T> create(final Field<T> newField, final boolean newForward, final T[][] newYDotK, + final FieldODEStateAndDerivative<T> newGlobalPreviousState, + final FieldODEStateAndDerivative<T> newGlobalCurrentState, + final FieldODEStateAndDerivative<T> newSoftPreviousState, + final FieldODEStateAndDerivative<T> newSoftCurrentState, + final FieldEquationsMapper<T> newMapper) { + return new MidpointFieldStepInterpolator<T>(newField, newForward, newYDotK, + newGlobalPreviousState, newGlobalCurrentState, + newSoftPreviousState, newSoftCurrentState, + newMapper); + } + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override + protected FieldODEStateAndDerivative<T> computeInterpolatedStateAndDerivatives(final FieldEquationsMapper<T> mapper, + final T time, final T theta, + final T thetaH, final T oneMinusThetaH) { + + final T coeffDot2 = theta.multiply(2); + final T coeffDot1 = time.getField().getOne().subtract(coeffDot2); + final T[] interpolatedState; + final T[] interpolatedDerivatives; + + if (getGlobalPreviousState() != null && theta.getReal() <= 0.5) { + final T coeff1 = theta.multiply(oneMinusThetaH); + final T coeff2 = theta.multiply(thetaH); + interpolatedState = previousStateLinearCombination(coeff1, coeff2); + interpolatedDerivatives = derivativeLinearCombination(coeffDot1, coeffDot2); + } else { + final T coeff1 = oneMinusThetaH.multiply(theta); + final T coeff2 = oneMinusThetaH.multiply(theta.add(1)).negate(); + interpolatedState = currentStateLinearCombination(coeff1, coeff2); + interpolatedDerivatives = derivativeLinearCombination(coeffDot1, coeffDot2); + } + + return new FieldODEStateAndDerivative<T>(time, interpolatedState, interpolatedDerivatives); + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/MidpointIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/MidpointIntegrator.java new file mode 100644 index 0000000..fa834a1 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/MidpointIntegrator.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + + +/** + * This class implements a second order Runge-Kutta integrator for + * Ordinary Differential Equations. + * + * <p>This method is an explicit Runge-Kutta method, its Butcher-array + * is the following one : + * <pre> + * 0 | 0 0 + * 1/2 | 1/2 0 + * |---------- + * | 0 1 + * </pre> + * </p> + * + * @see EulerIntegrator + * @see ClassicalRungeKuttaIntegrator + * @see GillIntegrator + * @see ThreeEighthesIntegrator + * @see LutherIntegrator + * + * @since 1.2 + */ + +public class MidpointIntegrator extends RungeKuttaIntegrator { + + /** Time steps Butcher array. */ + private static final double[] STATIC_C = { + 1.0 / 2.0 + }; + + /** Internal weights Butcher array. */ + private static final double[][] STATIC_A = { + { 1.0 / 2.0 } + }; + + /** Propagation weights Butcher array. */ + private static final double[] STATIC_B = { + 0.0, 1.0 + }; + + /** Simple constructor. + * Build a midpoint integrator with the given step. + * @param step integration step + */ + public MidpointIntegrator(final double step) { + super("midpoint", STATIC_C, STATIC_A, STATIC_B, new MidpointStepInterpolator(), step); + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/MidpointStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/MidpointStepInterpolator.java new file mode 100644 index 0000000..89a5dc1 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/MidpointStepInterpolator.java @@ -0,0 +1,116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.ode.sampling.StepInterpolator; + +/** + * This class implements a step interpolator for second order + * Runge-Kutta integrator. + * + * <p>This interpolator computes dense output inside the last + * step computed. The interpolation equation is consistent with the + * integration scheme : + * <ul> + * <li>Using reference point at step start:<br> + * y(t<sub>n</sub> + θ h) = y (t<sub>n</sub>) + θ h [(1 - θ) y'<sub>1</sub> + θ y'<sub>2</sub>] + * </li> + * <li>Using reference point at step end:<br> + * y(t<sub>n</sub> + θ h) = y (t<sub>n</sub> + h) + (1-θ) h [θ y'<sub>1</sub> - (1+θ) y'<sub>2</sub>] + * </li> + * </ul> + * </p> + * + * where θ belongs to [0 ; 1] and where y'<sub>1</sub> and y'<sub>2</sub> are the two + * evaluations of the derivatives already computed during the + * step.</p> + * + * @see MidpointIntegrator + * @since 1.2 + */ + +class MidpointStepInterpolator + extends RungeKuttaStepInterpolator { + + /** Serializable version identifier */ + private static final long serialVersionUID = 20111120L; + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link + * org.apache.commons.math3.ode.sampling.AbstractStepInterpolator#reinitialize} + * method should be called before using the instance in order to + * initialize the internal arrays. This constructor is used only + * in order to delay the initialization in some cases. The {@link + * RungeKuttaIntegrator} class uses the prototyping design pattern + * to create the step interpolators by cloning an uninitialized model + * and later initializing the copy. + */ + // CHECKSTYLE: stop RedundantModifier + // the public modifier here is needed for serialization + public MidpointStepInterpolator() { + } + // CHECKSTYLE: resume RedundantModifier + + /** Copy constructor. + * @param interpolator interpolator to copy from. The copy is a deep + * copy: its arrays are separated from the original arrays of the + * instance + */ + MidpointStepInterpolator(final MidpointStepInterpolator interpolator) { + super(interpolator); + } + + /** {@inheritDoc} */ + @Override + protected StepInterpolator doCopy() { + return new MidpointStepInterpolator(this); + } + + + /** {@inheritDoc} */ + @Override + protected void computeInterpolatedStateAndDerivatives(final double theta, + final double oneMinusThetaH) { + + final double coeffDot2 = 2 * theta; + final double coeffDot1 = 1 - coeffDot2; + + if ((previousState != null) && (theta <= 0.5)) { + final double coeff1 = theta * oneMinusThetaH; + final double coeff2 = theta * theta * h; + for (int i = 0; i < interpolatedState.length; ++i) { + final double yDot1 = yDotK[0][i]; + final double yDot2 = yDotK[1][i]; + interpolatedState[i] = previousState[i] + coeff1 * yDot1 + coeff2 * yDot2; + interpolatedDerivatives[i] = coeffDot1 * yDot1 + coeffDot2 * yDot2; + } + } else { + final double coeff1 = oneMinusThetaH * theta; + final double coeff2 = oneMinusThetaH * (1.0 + theta); + for (int i = 0; i < interpolatedState.length; ++i) { + final double yDot1 = yDotK[0][i]; + final double yDot2 = yDotK[1][i]; + interpolatedState[i] = currentState[i] + coeff1 * yDot1 - coeff2 * yDot2; + interpolatedDerivatives[i] = coeffDot1 * yDot1 + coeffDot2 * yDot2; + } + } + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/RungeKuttaFieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/RungeKuttaFieldIntegrator.java new file mode 100644 index 0000000..a97e9f5 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/RungeKuttaFieldIntegrator.java @@ -0,0 +1,273 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + + +import org.apache.commons.math3.Field; +import org.apache.commons.math3.RealFieldElement; +import org.apache.commons.math3.exception.DimensionMismatchException; +import org.apache.commons.math3.exception.MaxCountExceededException; +import org.apache.commons.math3.exception.NoBracketingException; +import org.apache.commons.math3.exception.NumberIsTooSmallException; +import org.apache.commons.math3.ode.AbstractFieldIntegrator; +import org.apache.commons.math3.ode.FieldEquationsMapper; +import org.apache.commons.math3.ode.FieldExpandableODE; +import org.apache.commons.math3.ode.FirstOrderFieldDifferentialEquations; +import org.apache.commons.math3.ode.FieldODEState; +import org.apache.commons.math3.ode.FieldODEStateAndDerivative; +import org.apache.commons.math3.util.MathArrays; + +/** + * This class implements the common part of all fixed step Runge-Kutta + * integrators for Ordinary Differential Equations. + * + * <p>These methods are explicit Runge-Kutta methods, their Butcher + * arrays are as follows : + * <pre> + * 0 | + * c2 | a21 + * c3 | a31 a32 + * ... | ... + * cs | as1 as2 ... ass-1 + * |-------------------------- + * | b1 b2 ... bs-1 bs + * </pre> + * </p> + * + * @see EulerFieldIntegrator + * @see ClassicalRungeKuttaFieldIntegrator + * @see GillFieldIntegrator + * @see MidpointFieldIntegrator + * @param <T> the type of the field elements + * @since 3.6 + */ + +public abstract class RungeKuttaFieldIntegrator<T extends RealFieldElement<T>> + extends AbstractFieldIntegrator<T> + implements FieldButcherArrayProvider<T> { + + /** Time steps from Butcher array (without the first zero). */ + private final T[] c; + + /** Internal weights from Butcher array (without the first empty row). */ + private final T[][] a; + + /** External weights for the high order method from Butcher array. */ + private final T[] b; + + /** Integration step. */ + private final T step; + + /** Simple constructor. + * Build a Runge-Kutta integrator with the given + * step. The default step handler does nothing. + * @param field field to which the time and state vector elements belong + * @param name name of the method + * @param step integration step + */ + protected RungeKuttaFieldIntegrator(final Field<T> field, final String name, final T step) { + super(field, name); + this.c = getC(); + this.a = getA(); + this.b = getB(); + this.step = step.abs(); + } + + /** Create a fraction. + * @param p numerator + * @param q denominator + * @return p/q computed in the instance field + */ + protected T fraction(final int p, final int q) { + return getField().getZero().add(p).divide(q); + } + + /** Create an interpolator. + * @param forward integration direction indicator + * @param yDotK slopes at the intermediate points + * @param globalPreviousState start of the global step + * @param globalCurrentState end of the global step + * @param mapper equations mapper for the all equations + * @return external weights for the high order method from Butcher array + */ + protected abstract RungeKuttaFieldStepInterpolator<T> createInterpolator(boolean forward, T[][] yDotK, + final FieldODEStateAndDerivative<T> globalPreviousState, + final FieldODEStateAndDerivative<T> globalCurrentState, + FieldEquationsMapper<T> mapper); + + /** {@inheritDoc} */ + public FieldODEStateAndDerivative<T> integrate(final FieldExpandableODE<T> equations, + final FieldODEState<T> initialState, final T finalTime) + throws NumberIsTooSmallException, DimensionMismatchException, + MaxCountExceededException, NoBracketingException { + + sanityChecks(initialState, finalTime); + final T t0 = initialState.getTime(); + final T[] y0 = equations.getMapper().mapState(initialState); + setStepStart(initIntegration(equations, t0, y0, finalTime)); + final boolean forward = finalTime.subtract(initialState.getTime()).getReal() > 0; + + // create some internal working arrays + final int stages = c.length + 1; + T[] y = y0; + final T[][] yDotK = MathArrays.buildArray(getField(), stages, -1); + final T[] yTmp = MathArrays.buildArray(getField(), y0.length); + + // set up integration control objects + if (forward) { + if (getStepStart().getTime().add(step).subtract(finalTime).getReal() >= 0) { + setStepSize(finalTime.subtract(getStepStart().getTime())); + } else { + setStepSize(step); + } + } else { + if (getStepStart().getTime().subtract(step).subtract(finalTime).getReal() <= 0) { + setStepSize(finalTime.subtract(getStepStart().getTime())); + } else { + setStepSize(step.negate()); + } + } + + // main integration loop + setIsLastStep(false); + do { + + // first stage + y = equations.getMapper().mapState(getStepStart()); + yDotK[0] = equations.getMapper().mapDerivative(getStepStart()); + + // next stages + for (int k = 1; k < stages; ++k) { + + for (int j = 0; j < y0.length; ++j) { + T sum = yDotK[0][j].multiply(a[k-1][0]); + for (int l = 1; l < k; ++l) { + sum = sum.add(yDotK[l][j].multiply(a[k-1][l])); + } + yTmp[j] = y[j].add(getStepSize().multiply(sum)); + } + + yDotK[k] = computeDerivatives(getStepStart().getTime().add(getStepSize().multiply(c[k-1])), yTmp); + + } + + // estimate the state at the end of the step + for (int j = 0; j < y0.length; ++j) { + T sum = yDotK[0][j].multiply(b[0]); + for (int l = 1; l < stages; ++l) { + sum = sum.add(yDotK[l][j].multiply(b[l])); + } + yTmp[j] = y[j].add(getStepSize().multiply(sum)); + } + final T stepEnd = getStepStart().getTime().add(getStepSize()); + final T[] yDotTmp = computeDerivatives(stepEnd, yTmp); + final FieldODEStateAndDerivative<T> stateTmp = new FieldODEStateAndDerivative<T>(stepEnd, yTmp, yDotTmp); + + // discrete events handling + System.arraycopy(yTmp, 0, y, 0, y0.length); + setStepStart(acceptStep(createInterpolator(forward, yDotK, getStepStart(), stateTmp, equations.getMapper()), + finalTime)); + + if (!isLastStep()) { + + // stepsize control for next step + final T nextT = getStepStart().getTime().add(getStepSize()); + final boolean nextIsLast = forward ? + (nextT.subtract(finalTime).getReal() >= 0) : + (nextT.subtract(finalTime).getReal() <= 0); + if (nextIsLast) { + setStepSize(finalTime.subtract(getStepStart().getTime())); + } + } + + } while (!isLastStep()); + + final FieldODEStateAndDerivative<T> finalState = getStepStart(); + setStepStart(null); + setStepSize(null); + return finalState; + + } + + /** Fast computation of a single step of ODE integration. + * <p>This method is intended for the limited use case of + * very fast computation of only one step without using any of the + * rich features of general integrators that may take some time + * to set up (i.e. no step handlers, no events handlers, no additional + * states, no interpolators, no error control, no evaluations count, + * no sanity checks ...). It handles the strict minimum of computation, + * so it can be embedded in outer loops.</p> + * <p> + * This method is <em>not</em> used at all by the {@link #integrate(FieldExpandableODE, + * FieldODEState, RealFieldElement)} method. It also completely ignores the step set at + * construction time, and uses only a single step to go from {@code t0} to {@code t}. + * </p> + * <p> + * As this method does not use any of the state-dependent features of the integrator, + * it should be reasonably thread-safe <em>if and only if</em> the provided differential + * equations are themselves thread-safe. + * </p> + * @param equations differential equations to integrate + * @param t0 initial time + * @param y0 initial value of the state vector at t0 + * @param t target time for the integration + * (can be set to a value smaller than {@code t0} for backward integration) + * @return state vector at {@code t} + */ + public T[] singleStep(final FirstOrderFieldDifferentialEquations<T> equations, + final T t0, final T[] y0, final T t) { + + // create some internal working arrays + final T[] y = y0.clone(); + final int stages = c.length + 1; + final T[][] yDotK = MathArrays.buildArray(getField(), stages, -1); + final T[] yTmp = y0.clone(); + + // first stage + final T h = t.subtract(t0); + yDotK[0] = equations.computeDerivatives(t0, y); + + // next stages + for (int k = 1; k < stages; ++k) { + + for (int j = 0; j < y0.length; ++j) { + T sum = yDotK[0][j].multiply(a[k-1][0]); + for (int l = 1; l < k; ++l) { + sum = sum.add(yDotK[l][j].multiply(a[k-1][l])); + } + yTmp[j] = y[j].add(h.multiply(sum)); + } + + yDotK[k] = equations.computeDerivatives(t0.add(h.multiply(c[k-1])), yTmp); + + } + + // estimate the state at the end of the step + for (int j = 0; j < y0.length; ++j) { + T sum = yDotK[0][j].multiply(b[0]); + for (int l = 1; l < stages; ++l) { + sum = sum.add(yDotK[l][j].multiply(b[l])); + } + y[j] = y[j].add(h.multiply(sum)); + } + + return y; + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/RungeKuttaFieldStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/RungeKuttaFieldStepInterpolator.java new file mode 100644 index 0000000..7d92d78 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/RungeKuttaFieldStepInterpolator.java @@ -0,0 +1,143 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.Field; +import org.apache.commons.math3.RealFieldElement; +import org.apache.commons.math3.ode.FieldEquationsMapper; +import org.apache.commons.math3.ode.FieldODEStateAndDerivative; +import org.apache.commons.math3.ode.sampling.AbstractFieldStepInterpolator; +import org.apache.commons.math3.util.MathArrays; + +/** This class represents an interpolator over the last step during an + * ODE integration for Runge-Kutta and embedded Runge-Kutta integrators. + * + * @see RungeKuttaFieldIntegrator + * @see EmbeddedRungeKuttaFieldIntegrator + * + * @param <T> the type of the field elements + * @since 3.6 + */ + +abstract class RungeKuttaFieldStepInterpolator<T extends RealFieldElement<T>> + extends AbstractFieldStepInterpolator<T> { + + /** Field to which the time and state vector elements belong. */ + private final Field<T> field; + + /** Slopes at the intermediate points. */ + private final T[][] yDotK; + + /** Simple constructor. + * @param field field to which the time and state vector elements belong + * @param forward integration direction indicator + * @param yDotK slopes at the intermediate points + * @param globalPreviousState start of the global step + * @param globalCurrentState end of the global step + * @param softPreviousState start of the restricted step + * @param softCurrentState end of the restricted step + * @param mapper equations mapper for the all equations + */ + protected RungeKuttaFieldStepInterpolator(final Field<T> field, final boolean forward, + final T[][] yDotK, + final FieldODEStateAndDerivative<T> globalPreviousState, + final FieldODEStateAndDerivative<T> globalCurrentState, + final FieldODEStateAndDerivative<T> softPreviousState, + final FieldODEStateAndDerivative<T> softCurrentState, + final FieldEquationsMapper<T> mapper) { + super(forward, globalPreviousState, globalCurrentState, softPreviousState, softCurrentState, mapper); + this.field = field; + this.yDotK = MathArrays.buildArray(field, yDotK.length, -1); + for (int i = 0; i < yDotK.length; ++i) { + this.yDotK[i] = yDotK[i].clone(); + } + } + + /** {@inheritDoc} */ + @Override + protected RungeKuttaFieldStepInterpolator<T> create(boolean newForward, + FieldODEStateAndDerivative<T> newGlobalPreviousState, + FieldODEStateAndDerivative<T> newGlobalCurrentState, + FieldODEStateAndDerivative<T> newSoftPreviousState, + FieldODEStateAndDerivative<T> newSoftCurrentState, + FieldEquationsMapper<T> newMapper) { + return create(field, newForward, yDotK, + newGlobalPreviousState, newGlobalCurrentState, + newSoftPreviousState, newSoftCurrentState, + newMapper); + } + + /** Create a new instance. + * @param newField field to which the time and state vector elements belong + * @param newForward integration direction indicator + * @param newYDotK slopes at the intermediate points + * @param newGlobalPreviousState start of the global step + * @param newGlobalCurrentState end of the global step + * @param newSoftPreviousState start of the restricted step + * @param newSoftCurrentState end of the restricted step + * @param newMapper equations mapper for the all equations + * @return a new instance + */ + protected abstract RungeKuttaFieldStepInterpolator<T> create(Field<T> newField, boolean newForward, T[][] newYDotK, + FieldODEStateAndDerivative<T> newGlobalPreviousState, + FieldODEStateAndDerivative<T> newGlobalCurrentState, + FieldODEStateAndDerivative<T> newSoftPreviousState, + FieldODEStateAndDerivative<T> newSoftCurrentState, + FieldEquationsMapper<T> newMapper); + + /** Compute a state by linear combination added to previous state. + * @param coefficients coefficients to apply to the method staged derivatives + * @return combined state + */ + protected final T[] previousStateLinearCombination(final T ... coefficients) { + return combine(getPreviousState().getState(), + coefficients); + } + + /** Compute a state by linear combination added to current state. + * @param coefficients coefficients to apply to the method staged derivatives + * @return combined state + */ + protected T[] currentStateLinearCombination(final T ... coefficients) { + return combine(getCurrentState().getState(), + coefficients); + } + + /** Compute a state derivative by linear combination. + * @param coefficients coefficients to apply to the method staged derivatives + * @return combined state + */ + protected T[] derivativeLinearCombination(final T ... coefficients) { + return combine(MathArrays.buildArray(field, yDotK[0].length), coefficients); + } + + /** Linearly combine arrays. + * @param a array to add to + * @param coefficients coefficients to apply to the method staged derivatives + * @return a itself, as a convenience for fluent API + */ + private T[] combine(final T[] a, final T ... coefficients) { + for (int i = 0; i < a.length; ++i) { + for (int k = 0; k < coefficients.length; ++k) { + a[i] = a[i].add(coefficients[k].multiply(yDotK[k][i])); + } + } + return a; + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/RungeKuttaIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/RungeKuttaIntegrator.java new file mode 100644 index 0000000..5f7d5d8 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/RungeKuttaIntegrator.java @@ -0,0 +1,269 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + + +import org.apache.commons.math3.exception.DimensionMismatchException; +import org.apache.commons.math3.exception.MaxCountExceededException; +import org.apache.commons.math3.exception.NoBracketingException; +import org.apache.commons.math3.exception.NumberIsTooSmallException; +import org.apache.commons.math3.ode.AbstractIntegrator; +import org.apache.commons.math3.ode.ExpandableStatefulODE; +import org.apache.commons.math3.ode.FirstOrderDifferentialEquations; +import org.apache.commons.math3.util.FastMath; + +/** + * This class implements the common part of all fixed step Runge-Kutta + * integrators for Ordinary Differential Equations. + * + * <p>These methods are explicit Runge-Kutta methods, their Butcher + * arrays are as follows : + * <pre> + * 0 | + * c2 | a21 + * c3 | a31 a32 + * ... | ... + * cs | as1 as2 ... ass-1 + * |-------------------------- + * | b1 b2 ... bs-1 bs + * </pre> + * </p> + * + * @see EulerIntegrator + * @see ClassicalRungeKuttaIntegrator + * @see GillIntegrator + * @see MidpointIntegrator + * @since 1.2 + */ + +public abstract class RungeKuttaIntegrator extends AbstractIntegrator { + + /** Time steps from Butcher array (without the first zero). */ + private final double[] c; + + /** Internal weights from Butcher array (without the first empty row). */ + private final double[][] a; + + /** External weights for the high order method from Butcher array. */ + private final double[] b; + + /** Prototype of the step interpolator. */ + private final RungeKuttaStepInterpolator prototype; + + /** Integration step. */ + private final double step; + + /** Simple constructor. + * Build a Runge-Kutta integrator with the given + * step. The default step handler does nothing. + * @param name name of the method + * @param c time steps from Butcher array (without the first zero) + * @param a internal weights from Butcher array (without the first empty row) + * @param b propagation weights for the high order method from Butcher array + * @param prototype prototype of the step interpolator to use + * @param step integration step + */ + protected RungeKuttaIntegrator(final String name, + final double[] c, final double[][] a, final double[] b, + final RungeKuttaStepInterpolator prototype, + final double step) { + super(name); + this.c = c; + this.a = a; + this.b = b; + this.prototype = prototype; + this.step = FastMath.abs(step); + } + + /** {@inheritDoc} */ + @Override + public void integrate(final ExpandableStatefulODE equations, final double t) + throws NumberIsTooSmallException, DimensionMismatchException, + MaxCountExceededException, NoBracketingException { + + sanityChecks(equations, t); + setEquations(equations); + final boolean forward = t > equations.getTime(); + + // create some internal working arrays + final double[] y0 = equations.getCompleteState(); + final double[] y = y0.clone(); + final int stages = c.length + 1; + final double[][] yDotK = new double[stages][]; + for (int i = 0; i < stages; ++i) { + yDotK [i] = new double[y0.length]; + } + final double[] yTmp = y0.clone(); + final double[] yDotTmp = new double[y0.length]; + + // set up an interpolator sharing the integrator arrays + final RungeKuttaStepInterpolator interpolator = (RungeKuttaStepInterpolator) prototype.copy(); + interpolator.reinitialize(this, yTmp, yDotK, forward, + equations.getPrimaryMapper(), equations.getSecondaryMappers()); + interpolator.storeTime(equations.getTime()); + + // set up integration control objects + stepStart = equations.getTime(); + if (forward) { + if (stepStart + step >= t) { + stepSize = t - stepStart; + } else { + stepSize = step; + } + } else { + if (stepStart - step <= t) { + stepSize = t - stepStart; + } else { + stepSize = -step; + } + } + initIntegration(equations.getTime(), y0, t); + + // main integration loop + isLastStep = false; + do { + + interpolator.shift(); + + // first stage + computeDerivatives(stepStart, y, yDotK[0]); + + // next stages + for (int k = 1; k < stages; ++k) { + + for (int j = 0; j < y0.length; ++j) { + double sum = a[k-1][0] * yDotK[0][j]; + for (int l = 1; l < k; ++l) { + sum += a[k-1][l] * yDotK[l][j]; + } + yTmp[j] = y[j] + stepSize * sum; + } + + computeDerivatives(stepStart + c[k-1] * stepSize, yTmp, yDotK[k]); + + } + + // estimate the state at the end of the step + for (int j = 0; j < y0.length; ++j) { + double sum = b[0] * yDotK[0][j]; + for (int l = 1; l < stages; ++l) { + sum += b[l] * yDotK[l][j]; + } + yTmp[j] = y[j] + stepSize * sum; + } + + // discrete events handling + interpolator.storeTime(stepStart + stepSize); + System.arraycopy(yTmp, 0, y, 0, y0.length); + System.arraycopy(yDotK[stages - 1], 0, yDotTmp, 0, y0.length); + stepStart = acceptStep(interpolator, y, yDotTmp, t); + + if (!isLastStep) { + + // prepare next step + interpolator.storeTime(stepStart); + + // stepsize control for next step + final double nextT = stepStart + stepSize; + final boolean nextIsLast = forward ? (nextT >= t) : (nextT <= t); + if (nextIsLast) { + stepSize = t - stepStart; + } + } + + } while (!isLastStep); + + // dispatch results + equations.setTime(stepStart); + equations.setCompleteState(y); + + stepStart = Double.NaN; + stepSize = Double.NaN; + + } + + /** Fast computation of a single step of ODE integration. + * <p>This method is intended for the limited use case of + * very fast computation of only one step without using any of the + * rich features of general integrators that may take some time + * to set up (i.e. no step handlers, no events handlers, no additional + * states, no interpolators, no error control, no evaluations count, + * no sanity checks ...). It handles the strict minimum of computation, + * so it can be embedded in outer loops.</p> + * <p> + * This method is <em>not</em> used at all by the {@link #integrate(ExpandableStatefulODE, double)} + * method. It also completely ignores the step set at construction time, and + * uses only a single step to go from {@code t0} to {@code t}. + * </p> + * <p> + * As this method does not use any of the state-dependent features of the integrator, + * it should be reasonably thread-safe <em>if and only if</em> the provided differential + * equations are themselves thread-safe. + * </p> + * @param equations differential equations to integrate + * @param t0 initial time + * @param y0 initial value of the state vector at t0 + * @param t target time for the integration + * (can be set to a value smaller than {@code t0} for backward integration) + * @return state vector at {@code t} + */ + public double[] singleStep(final FirstOrderDifferentialEquations equations, + final double t0, final double[] y0, final double t) { + + // create some internal working arrays + final double[] y = y0.clone(); + final int stages = c.length + 1; + final double[][] yDotK = new double[stages][]; + for (int i = 0; i < stages; ++i) { + yDotK [i] = new double[y0.length]; + } + final double[] yTmp = y0.clone(); + + // first stage + final double h = t - t0; + equations.computeDerivatives(t0, y, yDotK[0]); + + // next stages + for (int k = 1; k < stages; ++k) { + + for (int j = 0; j < y0.length; ++j) { + double sum = a[k-1][0] * yDotK[0][j]; + for (int l = 1; l < k; ++l) { + sum += a[k-1][l] * yDotK[l][j]; + } + yTmp[j] = y[j] + h * sum; + } + + equations.computeDerivatives(t0 + c[k-1] * h, yTmp, yDotK[k]); + + } + + // estimate the state at the end of the step + for (int j = 0; j < y0.length; ++j) { + double sum = b[0] * yDotK[0][j]; + for (int l = 1; l < stages; ++l) { + sum += b[l] * yDotK[l][j]; + } + y[j] += h * sum; + } + + return y; + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/RungeKuttaStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/RungeKuttaStepInterpolator.java new file mode 100644 index 0000000..1ae7cb9 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/RungeKuttaStepInterpolator.java @@ -0,0 +1,211 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +import org.apache.commons.math3.ode.AbstractIntegrator; +import org.apache.commons.math3.ode.EquationsMapper; +import org.apache.commons.math3.ode.sampling.AbstractStepInterpolator; + +/** This class represents an interpolator over the last step during an + * ODE integration for Runge-Kutta and embedded Runge-Kutta integrators. + * + * @see RungeKuttaIntegrator + * @see EmbeddedRungeKuttaIntegrator + * + * @since 1.2 + */ + +abstract class RungeKuttaStepInterpolator + extends AbstractStepInterpolator { + + /** Previous state. */ + protected double[] previousState; + + /** Slopes at the intermediate points */ + protected double[][] yDotK; + + /** Reference to the integrator. */ + protected AbstractIntegrator integrator; + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link #reinitialize} method should be called before using the + * instance in order to initialize the internal arrays. This + * constructor is used only in order to delay the initialization in + * some cases. The {@link RungeKuttaIntegrator} and {@link + * EmbeddedRungeKuttaIntegrator} classes use the prototyping design + * pattern to create the step interpolators by cloning an + * uninitialized model and latter initializing the copy. + */ + protected RungeKuttaStepInterpolator() { + previousState = null; + yDotK = null; + integrator = null; + } + + /** Copy constructor. + + * <p>The copied interpolator should have been finalized before the + * copy, otherwise the copy will not be able to perform correctly any + * interpolation and will throw a {@link NullPointerException} + * later. Since we don't want this constructor to throw the + * exceptions finalization may involve and since we don't want this + * method to modify the state of the copied interpolator, + * finalization is <strong>not</strong> done automatically, it + * remains under user control.</p> + + * <p>The copy is a deep copy: its arrays are separated from the + * original arrays of the instance.</p> + + * @param interpolator interpolator to copy from. + + */ + RungeKuttaStepInterpolator(final RungeKuttaStepInterpolator interpolator) { + + super(interpolator); + + if (interpolator.currentState != null) { + + previousState = interpolator.previousState.clone(); + + yDotK = new double[interpolator.yDotK.length][]; + for (int k = 0; k < interpolator.yDotK.length; ++k) { + yDotK[k] = interpolator.yDotK[k].clone(); + } + + } else { + previousState = null; + yDotK = null; + } + + // we cannot keep any reference to the equations in the copy + // the interpolator should have been finalized before + integrator = null; + + } + + /** Reinitialize the instance + * <p>Some Runge-Kutta integrators need fewer functions evaluations + * than their counterpart step interpolators. So the interpolator + * should perform the last evaluations they need by themselves. The + * {@link RungeKuttaIntegrator RungeKuttaIntegrator} and {@link + * EmbeddedRungeKuttaIntegrator EmbeddedRungeKuttaIntegrator} + * abstract classes call this method in order to let the step + * interpolator perform the evaluations it needs. These evaluations + * will be performed during the call to <code>doFinalize</code> if + * any, i.e. only if the step handler either calls the {@link + * AbstractStepInterpolator#finalizeStep finalizeStep} method or the + * {@link AbstractStepInterpolator#getInterpolatedState + * getInterpolatedState} method (for an interpolator which needs a + * finalization) or if it clones the step interpolator.</p> + * @param rkIntegrator integrator being used + * @param y reference to the integrator array holding the state at + * the end of the step + * @param yDotArray reference to the integrator array holding all the + * intermediate slopes + * @param forward integration direction indicator + * @param primaryMapper equations mapper for the primary equations set + * @param secondaryMappers equations mappers for the secondary equations sets + */ + public void reinitialize(final AbstractIntegrator rkIntegrator, + final double[] y, final double[][] yDotArray, final boolean forward, + final EquationsMapper primaryMapper, + final EquationsMapper[] secondaryMappers) { + reinitialize(y, forward, primaryMapper, secondaryMappers); + this.previousState = null; + this.yDotK = yDotArray; + this.integrator = rkIntegrator; + } + + /** {@inheritDoc} */ + @Override + public void shift() { + previousState = currentState.clone(); + super.shift(); + } + + /** {@inheritDoc} */ + @Override + public void writeExternal(final ObjectOutput out) + throws IOException { + + // save the state of the base class + writeBaseExternal(out); + + // save the local attributes + final int n = (currentState == null) ? -1 : currentState.length; + for (int i = 0; i < n; ++i) { + out.writeDouble(previousState[i]); + } + + final int kMax = (yDotK == null) ? -1 : yDotK.length; + out.writeInt(kMax); + for (int k = 0; k < kMax; ++k) { + for (int i = 0; i < n; ++i) { + out.writeDouble(yDotK[k][i]); + } + } + + // we do not save any reference to the equations + + } + + /** {@inheritDoc} */ + @Override + public void readExternal(final ObjectInput in) + throws IOException, ClassNotFoundException { + + // read the base class + final double t = readBaseExternal(in); + + // read the local attributes + final int n = (currentState == null) ? -1 : currentState.length; + if (n < 0) { + previousState = null; + } else { + previousState = new double[n]; + for (int i = 0; i < n; ++i) { + previousState[i] = in.readDouble(); + } + } + + final int kMax = in.readInt(); + yDotK = (kMax < 0) ? null : new double[kMax][]; + for (int k = 0; k < kMax; ++k) { + yDotK[k] = (n < 0) ? null : new double[n]; + for (int i = 0; i < n; ++i) { + yDotK[k][i] = in.readDouble(); + } + } + + integrator = null; + + if (currentState != null) { + // we can now set the interpolated time and state + setInterpolatedTime(t); + } else { + interpolatedTime = t; + } + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/ThreeEighthesFieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/ThreeEighthesFieldIntegrator.java new file mode 100644 index 0000000..7e91de8 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/ThreeEighthesFieldIntegrator.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.Field; +import org.apache.commons.math3.RealFieldElement; +import org.apache.commons.math3.ode.FieldEquationsMapper; +import org.apache.commons.math3.ode.FieldODEStateAndDerivative; +import org.apache.commons.math3.util.MathArrays; + +/** + * This class implements the 3/8 fourth order Runge-Kutta + * integrator for Ordinary Differential Equations. + * + * <p>This method is an explicit Runge-Kutta method, its Butcher-array + * is the following one : + * <pre> + * 0 | 0 0 0 0 + * 1/3 | 1/3 0 0 0 + * 2/3 |-1/3 1 0 0 + * 1 | 1 -1 1 0 + * |-------------------- + * | 1/8 3/8 3/8 1/8 + * </pre> + * </p> + * + * @see EulerFieldIntegrator + * @see ClassicalRungeKuttaFieldIntegrator + * @see GillFieldIntegrator + * @see MidpointFieldIntegrator + * @see LutherFieldIntegrator + * @param <T> the type of the field elements + * @since 3.6 + */ + +public class ThreeEighthesFieldIntegrator<T extends RealFieldElement<T>> + extends RungeKuttaFieldIntegrator<T> { + + /** Simple constructor. + * Build a 3/8 integrator with the given step. + * @param field field to which the time and state vector elements belong + * @param step integration step + */ + public ThreeEighthesFieldIntegrator(final Field<T> field, final T step) { + super(field, "3/8", step); + } + + /** {@inheritDoc} */ + public T[] getC() { + final T[] c = MathArrays.buildArray(getField(), 3); + c[0] = fraction(1, 3); + c[1] = c[0].add(c[0]); + c[2] = getField().getOne(); + return c; + } + + /** {@inheritDoc} */ + public T[][] getA() { + final T[][] a = MathArrays.buildArray(getField(), 3, -1); + for (int i = 0; i < a.length; ++i) { + a[i] = MathArrays.buildArray(getField(), i + 1); + } + a[0][0] = fraction(1, 3); + a[1][0] = a[0][0].negate(); + a[1][1] = getField().getOne(); + a[2][0] = getField().getOne(); + a[2][1] = getField().getOne().negate(); + a[2][2] = getField().getOne(); + return a; + } + + /** {@inheritDoc} */ + public T[] getB() { + final T[] b = MathArrays.buildArray(getField(), 4); + b[0] = fraction(1, 8); + b[1] = fraction(3, 8); + b[2] = b[1]; + b[3] = b[0]; + return b; + } + + /** {@inheritDoc} */ + @Override + protected ThreeEighthesFieldStepInterpolator<T> + createInterpolator(final boolean forward, T[][] yDotK, + final FieldODEStateAndDerivative<T> globalPreviousState, + final FieldODEStateAndDerivative<T> globalCurrentState, + final FieldEquationsMapper<T> mapper) { + return new ThreeEighthesFieldStepInterpolator<T>(getField(), forward, yDotK, + globalPreviousState, globalCurrentState, + globalPreviousState, globalCurrentState, + mapper); + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/ThreeEighthesFieldStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/ThreeEighthesFieldStepInterpolator.java new file mode 100644 index 0000000..14a4eb8 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/ThreeEighthesFieldStepInterpolator.java @@ -0,0 +1,139 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.Field; +import org.apache.commons.math3.RealFieldElement; +import org.apache.commons.math3.ode.FieldEquationsMapper; +import org.apache.commons.math3.ode.FieldODEStateAndDerivative; + +/** + * This class implements a step interpolator for the 3/8 fourth + * order Runge-Kutta integrator. + * + * <p>This interpolator allows to compute dense output inside the last + * step computed. The interpolation equation is consistent with the + * integration scheme : + * <ul> + * <li>Using reference point at step start:<br> + * y(t<sub>n</sub> + θ h) = y (t<sub>n</sub>) + * + θ (h/8) [ (8 - 15 θ + 8 θ<sup>2</sup>) y'<sub>1</sub> + * + 3 * (15 θ - 12 θ<sup>2</sup>) y'<sub>2</sub> + * + 3 θ y'<sub>3</sub> + * + (-3 θ + 4 θ<sup>2</sup>) y'<sub>4</sub> + * ] + * </li> + * <li>Using reference point at step end:<br> + * y(t<sub>n</sub> + θ h) = y (t<sub>n</sub> + h) + * - (1 - θ) (h/8) [(1 - 7 θ + 8 θ<sup>2</sup>) y'<sub>1</sub> + * + 3 (1 + θ - 4 θ<sup>2</sup>) y'<sub>2</sub> + * + 3 (1 + θ) y'<sub>3</sub> + * + (1 + θ + 4 θ<sup>2</sup>) y'<sub>4</sub> + * ] + * </li> + * </ul> + * </p> + * + * where θ belongs to [0 ; 1] and where y'<sub>1</sub> to y'<sub>4</sub> are the four + * evaluations of the derivatives already computed during the + * step.</p> + * + * @see ThreeEighthesFieldIntegrator + * @param <T> the type of the field elements + * @since 3.6 + */ + +class ThreeEighthesFieldStepInterpolator<T extends RealFieldElement<T>> + extends RungeKuttaFieldStepInterpolator<T> { + + /** Simple constructor. + * @param field field to which the time and state vector elements belong + * @param forward integration direction indicator + * @param yDotK slopes at the intermediate points + * @param globalPreviousState start of the global step + * @param globalCurrentState end of the global step + * @param softPreviousState start of the restricted step + * @param softCurrentState end of the restricted step + * @param mapper equations mapper for the all equations + */ + ThreeEighthesFieldStepInterpolator(final Field<T> field, final boolean forward, + final T[][] yDotK, + final FieldODEStateAndDerivative<T> globalPreviousState, + final FieldODEStateAndDerivative<T> globalCurrentState, + final FieldODEStateAndDerivative<T> softPreviousState, + final FieldODEStateAndDerivative<T> softCurrentState, + final FieldEquationsMapper<T> mapper) { + super(field, forward, yDotK, + globalPreviousState, globalCurrentState, softPreviousState, softCurrentState, + mapper); + } + + /** {@inheritDoc} */ + @Override + protected ThreeEighthesFieldStepInterpolator<T> create(final Field<T> newField, final boolean newForward, final T[][] newYDotK, + final FieldODEStateAndDerivative<T> newGlobalPreviousState, + final FieldODEStateAndDerivative<T> newGlobalCurrentState, + final FieldODEStateAndDerivative<T> newSoftPreviousState, + final FieldODEStateAndDerivative<T> newSoftCurrentState, + final FieldEquationsMapper<T> newMapper) { + return new ThreeEighthesFieldStepInterpolator<T>(newField, newForward, newYDotK, + newGlobalPreviousState, newGlobalCurrentState, + newSoftPreviousState, newSoftCurrentState, + newMapper); + } + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override + protected FieldODEStateAndDerivative<T> computeInterpolatedStateAndDerivatives(final FieldEquationsMapper<T> mapper, + final T time, final T theta, + final T thetaH, final T oneMinusThetaH) { + + final T coeffDot3 = theta.multiply(0.75); + final T coeffDot1 = coeffDot3.multiply(theta.multiply(4).subtract(5)).add(1); + final T coeffDot2 = coeffDot3.multiply(theta.multiply(-6).add(5)); + final T coeffDot4 = coeffDot3.multiply(theta.multiply(2).subtract(1)); + final T[] interpolatedState; + final T[] interpolatedDerivatives; + + if (getGlobalPreviousState() != null && theta.getReal() <= 0.5) { + final T s = thetaH.divide(8); + final T fourTheta2 = theta.multiply(theta).multiply(4); + final T coeff1 = s.multiply(fourTheta2.multiply(2).subtract(theta.multiply(15)).add(8)); + final T coeff2 = s.multiply(theta.multiply(5).subtract(fourTheta2)).multiply(3); + final T coeff3 = s.multiply(theta).multiply(3); + final T coeff4 = s.multiply(fourTheta2.subtract(theta.multiply(3))); + interpolatedState = previousStateLinearCombination(coeff1, coeff2, coeff3, coeff4); + interpolatedDerivatives = derivativeLinearCombination(coeffDot1, coeffDot2, coeffDot3, coeffDot4); + } else { + final T s = oneMinusThetaH.divide(-8); + final T fourTheta2 = theta.multiply(theta).multiply(4); + final T thetaPlus1 = theta.add(1); + final T coeff1 = s.multiply(fourTheta2.multiply(2).subtract(theta.multiply(7)).add(1)); + final T coeff2 = s.multiply(thetaPlus1.subtract(fourTheta2)).multiply(3); + final T coeff3 = s.multiply(thetaPlus1).multiply(3); + final T coeff4 = s.multiply(thetaPlus1.add(fourTheta2)); + interpolatedState = currentStateLinearCombination(coeff1, coeff2, coeff3, coeff4); + interpolatedDerivatives = derivativeLinearCombination(coeffDot1, coeffDot2, coeffDot3, coeffDot4); + } + + return new FieldODEStateAndDerivative<T>(time, interpolatedState, interpolatedDerivatives); + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/ThreeEighthesIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/ThreeEighthesIntegrator.java new file mode 100644 index 0000000..c5f8216 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/ThreeEighthesIntegrator.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + + +/** + * This class implements the 3/8 fourth order Runge-Kutta + * integrator for Ordinary Differential Equations. + * + * <p>This method is an explicit Runge-Kutta method, its Butcher-array + * is the following one : + * <pre> + * 0 | 0 0 0 0 + * 1/3 | 1/3 0 0 0 + * 2/3 |-1/3 1 0 0 + * 1 | 1 -1 1 0 + * |-------------------- + * | 1/8 3/8 3/8 1/8 + * </pre> + * </p> + * + * @see EulerIntegrator + * @see ClassicalRungeKuttaIntegrator + * @see GillIntegrator + * @see MidpointIntegrator + * @see LutherIntegrator + * @since 1.2 + */ + +public class ThreeEighthesIntegrator extends RungeKuttaIntegrator { + + /** Time steps Butcher array. */ + private static final double[] STATIC_C = { + 1.0 / 3.0, 2.0 / 3.0, 1.0 + }; + + /** Internal weights Butcher array. */ + private static final double[][] STATIC_A = { + { 1.0 / 3.0 }, + { -1.0 / 3.0, 1.0 }, + { 1.0, -1.0, 1.0 } + }; + + /** Propagation weights Butcher array. */ + private static final double[] STATIC_B = { + 1.0 / 8.0, 3.0 / 8.0, 3.0 / 8.0, 1.0 / 8.0 + }; + + /** Simple constructor. + * Build a 3/8 integrator with the given step. + * @param step integration step + */ + public ThreeEighthesIntegrator(final double step) { + super("3/8", STATIC_C, STATIC_A, STATIC_B, new ThreeEighthesStepInterpolator(), step); + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/ThreeEighthesStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/ThreeEighthesStepInterpolator.java new file mode 100644 index 0000000..df288fd --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/ThreeEighthesStepInterpolator.java @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.ode.nonstiff; + +import org.apache.commons.math3.ode.sampling.StepInterpolator; + +/** + * This class implements a step interpolator for the 3/8 fourth + * order Runge-Kutta integrator. + * + * <p>This interpolator allows to compute dense output inside the last + * step computed. The interpolation equation is consistent with the + * integration scheme : + * <ul> + * <li>Using reference point at step start:<br> + * y(t<sub>n</sub> + θ h) = y (t<sub>n</sub>) + * + θ (h/8) [ (8 - 15 θ + 8 θ<sup>2</sup>) y'<sub>1</sub> + * + 3 * (15 θ - 12 θ<sup>2</sup>) y'<sub>2</sub> + * + 3 θ y'<sub>3</sub> + * + (-3 θ + 4 θ<sup>2</sup>) y'<sub>4</sub> + * ] + * </li> + * <li>Using reference point at step end:<br> + * y(t<sub>n</sub> + θ h) = y (t<sub>n</sub> + h) + * - (1 - θ) (h/8) [(1 - 7 θ + 8 θ<sup>2</sup>) y'<sub>1</sub> + * + 3 (1 + θ - 4 θ<sup>2</sup>) y'<sub>2</sub> + * + 3 (1 + θ) y'<sub>3</sub> + * + (1 + θ + 4 θ<sup>2</sup>) y'<sub>4</sub> + * ] + * </li> + * </ul> + * </p> + * + * where θ belongs to [0 ; 1] and where y'<sub>1</sub> to y'<sub>4</sub> are the four + * evaluations of the derivatives already computed during the + * step.</p> + * + * @see ThreeEighthesIntegrator + * @since 1.2 + */ + +class ThreeEighthesStepInterpolator + extends RungeKuttaStepInterpolator { + + /** Serializable version identifier */ + private static final long serialVersionUID = 20111120L; + + /** Simple constructor. + * This constructor builds an instance that is not usable yet, the + * {@link + * org.apache.commons.math3.ode.sampling.AbstractStepInterpolator#reinitialize} + * method should be called before using the instance in order to + * initialize the internal arrays. This constructor is used only + * in order to delay the initialization in some cases. The {@link + * RungeKuttaIntegrator} class uses the prototyping design pattern + * to create the step interpolators by cloning an uninitialized model + * and later initializing the copy. + */ + // CHECKSTYLE: stop RedundantModifier + // the public modifier here is needed for serialization + public ThreeEighthesStepInterpolator() { + } + // CHECKSTYLE: resume RedundantModifier + + /** Copy constructor. + * @param interpolator interpolator to copy from. The copy is a deep + * copy: its arrays are separated from the original arrays of the + * instance + */ + ThreeEighthesStepInterpolator(final ThreeEighthesStepInterpolator interpolator) { + super(interpolator); + } + + /** {@inheritDoc} */ + @Override + protected StepInterpolator doCopy() { + return new ThreeEighthesStepInterpolator(this); + } + + + /** {@inheritDoc} */ + @Override + protected void computeInterpolatedStateAndDerivatives(final double theta, + final double oneMinusThetaH) { + + final double coeffDot3 = 0.75 * theta; + final double coeffDot1 = coeffDot3 * (4 * theta - 5) + 1; + final double coeffDot2 = coeffDot3 * (5 - 6 * theta); + final double coeffDot4 = coeffDot3 * (2 * theta - 1); + + if ((previousState != null) && (theta <= 0.5)) { + final double s = theta * h / 8.0; + final double fourTheta2 = 4 * theta * theta; + final double coeff1 = s * (8 - 15 * theta + 2 * fourTheta2); + final double coeff2 = 3 * s * (5 * theta - fourTheta2); + final double coeff3 = 3 * s * theta; + final double coeff4 = s * (-3 * theta + fourTheta2); + for (int i = 0; i < interpolatedState.length; ++i) { + final double yDot1 = yDotK[0][i]; + final double yDot2 = yDotK[1][i]; + final double yDot3 = yDotK[2][i]; + final double yDot4 = yDotK[3][i]; + interpolatedState[i] = + previousState[i] + coeff1 * yDot1 + coeff2 * yDot2 + coeff3 * yDot3 + coeff4 * yDot4; + interpolatedDerivatives[i] = + coeffDot1 * yDot1 + coeffDot2 * yDot2 + coeffDot3 * yDot3 + coeffDot4 * yDot4; + + } + } else { + final double s = oneMinusThetaH / 8.0; + final double fourTheta2 = 4 * theta * theta; + final double coeff1 = s * (1 - 7 * theta + 2 * fourTheta2); + final double coeff2 = 3 * s * (1 + theta - fourTheta2); + final double coeff3 = 3 * s * (1 + theta); + final double coeff4 = s * (1 + theta + fourTheta2); + for (int i = 0; i < interpolatedState.length; ++i) { + final double yDot1 = yDotK[0][i]; + final double yDot2 = yDotK[1][i]; + final double yDot3 = yDotK[2][i]; + final double yDot4 = yDotK[3][i]; + interpolatedState[i] = + currentState[i] - coeff1 * yDot1 - coeff2 * yDot2 - coeff3 * yDot3 - coeff4 * yDot4; + interpolatedDerivatives[i] = + coeffDot1 * yDot1 + coeffDot2 * yDot2 + coeffDot3 * yDot3 + coeffDot4 * yDot4; + + } + } + + } + +} diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/package-info.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/package-info.java new file mode 100644 index 0000000..b2387ce --- /dev/null +++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/package-info.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + * <p> + * This package provides classes to solve non-stiff Ordinary Differential Equations problems. + * </p> + * + * + */ +package org.apache.commons.math3.ode.nonstiff; |