Field-based version of Dormand-Prince 5(4) method for solving ODE.

This commit is contained in:
Luc Maisonobe 2016-01-06 12:24:44 +01:00
parent 301b0a8110
commit e7a46ac6ca
2 changed files with 417 additions and 0 deletions

View File

@ -0,0 +1,172 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.math4.ode.nonstiff;
import org.apache.commons.math4.Field;
import org.apache.commons.math4.RealFieldElement;
import org.apache.commons.math4.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)";
/** 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 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, true, STATIC_C, STATIC_A, STATIC_B,
new DormandPrince54FieldStepInterpolator<T>(),
minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance);
}
/** 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, true, STATIC_C, STATIC_A, STATIC_B,
new DormandPrince54FieldStepInterpolator<T>(),
minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance);
}
/** {@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();
}
}

View File

@ -0,0 +1,245 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.math4.ode.nonstiff;
import org.apache.commons.math4.RealFieldElement;
import org.apache.commons.math4.ode.FieldEquationsMapper;
import org.apache.commons.math4.ode.FieldODEStateAndDerivative;
import org.apache.commons.math4.util.MathArrays;
/**
* 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 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;
/** First vector for interpolation. */
private T[] v1;
/** Second vector for interpolation. */
private T[] v2;
/** Third vector for interpolation. */
private T[] v3;
/** Fourth vector for interpolation. */
private T[] 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.
*/
DormandPrince54FieldStepInterpolator() {
super();
v1 = null;
v2 = null;
v3 = null;
v4 = null;
vectorsInitialized = false;
}
/** Copy constructor.
* @param interpolator interpolator to copy from. The copy is a deep
* copy: its arrays are separated from the original arrays of the
* instance
*/
DormandPrince54FieldStepInterpolator(final DormandPrince54FieldStepInterpolator<T> 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 DormandPrince54FieldStepInterpolator<T> doCopy() {
return new DormandPrince54FieldStepInterpolator<T>(this);
}
/** {@inheritDoc} */
@Override
protected void reinitialize(final T[] y, final boolean isForward, final FieldEquationsMapper<T> equationsMapper) {
super.reinitialize(y, isForward, equationsMapper);
v1 = null;
v2 = null;
v3 = null;
v4 = null;
vectorsInitialized = false;
}
/** {@inheritDoc} */
@Override
public void storeState(final FieldODEStateAndDerivative<T> state) {
super.storeState(state);
vectorsInitialized = false;
}
/** {@inheritDoc} */
@Override
protected FieldODEStateAndDerivative<T> computeInterpolatedStateAndDerivatives(final FieldEquationsMapper<T> mapper,
final T time, final T theta,
final T oneMinusThetaH) {
if (! vectorsInitialized) {
if (v1 == null) {
v1 = MathArrays.buildArray(time.getField(), previousState.length);
v2 = MathArrays.buildArray(time.getField(), previousState.length);
v3 = MathArrays.buildArray(time.getField(), previousState.length);
v4 = MathArrays.buildArray(time.getField(), previousState.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 < previousState.length; ++i) {
final T yDot0 = yDotK[0][i];
final T yDot2 = yDotK[2][i];
final T yDot3 = yDotK[3][i];
final T yDot4 = yDotK[4][i];
final T yDot5 = yDotK[5][i];
final T yDot6 = yDotK[6][i];
v1[i] = yDot0.multiply(A70).
add(yDot2.multiply(A72)).
add(yDot3.multiply(A73)).
add(yDot4.multiply(A74)).
add(yDot5.multiply(A75));
v2[i] = yDot0.subtract(v1[i]);
v3[i] = v1[i].subtract(v2[i]).subtract(yDot6);
v4[i] = yDot0.multiply(D0).
add(yDot2.multiply(D2)).
add(yDot3.multiply(D3)).
add(yDot4.multiply(D4)).
add(yDot5.multiply(D5)).
add(yDot6.multiply(D6));
}
vectorsInitialized = true;
}
// interpolate
final T one = theta.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 = MathArrays.buildArray(theta.getField(), previousState.length);
final T[] interpolatedDerivatives = MathArrays.buildArray(theta.getField(), previousState.length);
if ((previousState != null) && (theta.getReal() <= 0.5)) {
for (int i = 0; i < interpolatedState.length; ++i) {
interpolatedState[i] = previousState[i].
add(theta.multiply(h.multiply(v1[i].
add(eta.multiply(v2[i].
add(theta.multiply(v3[i].
add(eta.multiply(v4[i])))))))));
interpolatedDerivatives[i] = v1[i].
add(dot2.multiply(v2[i])).
add(dot3.multiply(v3[i])).
add(dot4.multiply(v4[i]));
}
} else {
for (int i = 0; i < interpolatedState.length; ++i) {
interpolatedState[i] = currentState[i].
subtract(oneMinusThetaH.multiply(v1[i].
subtract(theta.multiply(v2[i].
add(theta.multiply(v3[i].
add(eta.multiply(v4[i]))))))));
interpolatedDerivatives[i] = v1[i].
add(dot2.multiply(v2[i])).
add(dot3.multiply(v3[i])).
add(dot4.multiply(v4[i]));
}
}
return new FieldODEStateAndDerivative<T>(time, interpolatedState, yDotK[0]);
}
}