AdamsNordsieckFieldTransformer.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      https://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. /*
  18.  * This is not the original file distributed by the Apache Software Foundation
  19.  * It has been modified by the Hipparchus project
  20.  */

  21. package org.hipparchus.ode.nonstiff;

  22. import java.util.Arrays;
  23. import java.util.HashMap;
  24. import java.util.Map;

  25. import org.hipparchus.Field;
  26. import org.hipparchus.CalculusFieldElement;
  27. import org.hipparchus.linear.Array2DRowFieldMatrix;
  28. import org.hipparchus.linear.ArrayFieldVector;
  29. import org.hipparchus.linear.FieldDecompositionSolver;
  30. import org.hipparchus.linear.FieldLUDecomposition;
  31. import org.hipparchus.linear.FieldMatrix;
  32. import org.hipparchus.util.MathArrays;

  33. /** Transformer to Nordsieck vectors for Adams integrators.
  34.  * <p>This class is used by {@link AdamsBashforthIntegrator Adams-Bashforth} and
  35.  * {@link AdamsMoultonIntegrator Adams-Moulton} integrators to convert between
  36.  * classical representation with several previous first derivatives and Nordsieck
  37.  * representation with higher order scaled derivatives.</p>
  38.  *
  39.  * <p>We define scaled derivatives s<sub>i</sub>(n) at step n as:
  40.  * \[
  41.  *   \left\{\begin{align}
  42.  *   s_1(n) &amp;= h y'_n \text{ for first derivative}\\
  43.  *   s_2(n) &amp;= \frac{h^2}{2} y_n'' \text{ for second derivative}\\
  44.  *   s_3(n) &amp;= \frac{h^3}{6} y_n''' \text{ for third derivative}\\
  45.  *   &amp;\cdots\\
  46.  *   s_k(n) &amp;= \frac{h^k}{k!} y_n^{(k)} \text{ for } k^\mathrm{th} \text{ derivative}
  47.  *   \end{align}\right.
  48.  * \]</p>
  49.  *
  50.  * <p>With the previous definition, the classical representation of multistep methods
  51.  * uses first derivatives only, i.e. it handles y<sub>n</sub>, s<sub>1</sub>(n) and
  52.  * q<sub>n</sub> where q<sub>n</sub> is defined as:
  53.  * \[
  54.  *   q_n = [ s_1(n-1) s_1(n-2) \ldots s_1(n-(k-1)) ]^T
  55.  * \]
  56.  * (we omit the k index in the notation for clarity).</p>
  57.  *
  58.  * <p>Another possible representation uses the Nordsieck vector with
  59.  * higher degrees scaled derivatives all taken at the same step, i.e it handles y<sub>n</sub>,
  60.  * s<sub>1</sub>(n) and r<sub>n</sub>) where r<sub>n</sub> is defined as:
  61.  * \[
  62.  * r_n = [ s_2(n), s_3(n) \ldots s_k(n) ]^T
  63.  * \]
  64.  * (here again we omit the k index in the notation for clarity)
  65.  * </p>
  66.  *
  67.  * <p>Taylor series formulas show that for any index offset i, s<sub>1</sub>(n-i) can be
  68.  * computed from s<sub>1</sub>(n), s<sub>2</sub>(n) ... s<sub>k</sub>(n), the formula being exact
  69.  * for degree k polynomials.
  70.  * \[
  71.  * s_1(n-i) = s_1(n) + \sum_{j\gt 0} (j+1) (-i)^j s_{j+1}(n)
  72.  * \]
  73.  * The previous formula can be used with several values for i to compute the transform between
  74.  * classical representation and Nordsieck vector at step end. The transform between r<sub>n</sub>
  75.  * and q<sub>n</sub> resulting from the Taylor series formulas above is:
  76.  * \[
  77.  * q_n = s_1(n) u + P r_n
  78.  * \]
  79.  * where u is the [ 1 1 ... 1 ]<sup>T</sup> vector and P is the (k-1)&times;(k-1) matrix built
  80.  * with the \((j+1) (-i)^j\) terms with i being the row number starting from 1 and j being
  81.  * the column number starting from 1:
  82.  * \[
  83.  *   P=\begin{bmatrix}
  84.  *   -2  &amp;  3 &amp;   -4 &amp;    5 &amp; \ldots \\
  85.  *   -4  &amp; 12 &amp;  -32 &amp;   80 &amp; \ldots \\
  86.  *   -6  &amp; 27 &amp; -108 &amp;  405 &amp; \ldots \\
  87.  *   -8  &amp; 48 &amp; -256 &amp; 1280 &amp; \ldots \\
  88.  *       &amp;    &amp;  \ldots\\
  89.  *    \end{bmatrix}
  90.  * \]
  91.  *
  92.  * <p>Changing -i into +i in the formula above can be used to compute a similar transform between
  93.  * classical representation and Nordsieck vector at step start. The resulting matrix is simply
  94.  * the absolute value of matrix P.</p>
  95.  *
  96.  * <p>For {@link AdamsBashforthIntegrator Adams-Bashforth} method, the Nordsieck vector
  97.  * at step n+1 is computed from the Nordsieck vector at step n as follows:
  98.  * <ul>
  99.  *   <li>y<sub>n+1</sub> = y<sub>n</sub> + s<sub>1</sub>(n) + u<sup>T</sup> r<sub>n</sub></li>
  100.  *   <li>s<sub>1</sub>(n+1) = h f(t<sub>n+1</sub>, y<sub>n+1</sub>)</li>
  101.  *   <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>
  102.  * </ul>
  103.  * <p>where A is a rows shifting matrix (the lower left part is an identity matrix):</p>
  104.  * <pre>
  105.  *        [ 0 0   ...  0 0 | 0 ]
  106.  *        [ ---------------+---]
  107.  *        [ 1 0   ...  0 0 | 0 ]
  108.  *    A = [ 0 1   ...  0 0 | 0 ]
  109.  *        [       ...      | 0 ]
  110.  *        [ 0 0   ...  1 0 | 0 ]
  111.  *        [ 0 0   ...  0 1 | 0 ]
  112.  * </pre>
  113.  *
  114.  * <p>For {@link AdamsMoultonIntegrator Adams-Moulton} method, the predicted Nordsieck vector
  115.  * at step n+1 is computed from the Nordsieck vector at step n as follows:
  116.  * <ul>
  117.  *   <li>Y<sub>n+1</sub> = y<sub>n</sub> + s<sub>1</sub>(n) + u<sup>T</sup> r<sub>n</sub></li>
  118.  *   <li>S<sub>1</sub>(n+1) = h f(t<sub>n+1</sub>, Y<sub>n+1</sub>)</li>
  119.  *   <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>
  120.  * </ul>
  121.  * From this predicted vector, the corrected vector is computed as follows:
  122.  * <ul>
  123.  *   <li>y<sub>n+1</sub> = y<sub>n</sub> + S<sub>1</sub>(n+1) + [ -1 +1 -1 +1 ... &plusmn;1 ] r<sub>n+1</sub></li>
  124.  *   <li>s<sub>1</sub>(n+1) = h f(t<sub>n+1</sub>, y<sub>n+1</sub>)</li>
  125.  *   <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>
  126.  * </ul>
  127.  * <p>where the upper case Y<sub>n+1</sub>, S<sub>1</sub>(n+1) and R<sub>n+1</sub> represent the
  128.  * predicted states whereas the lower case y<sub>n+1</sub>, s<sub>n+1</sub> and r<sub>n+1</sub>
  129.  * represent the corrected states.</p>
  130.  *
  131.  * <p>We observe that both methods use similar update formulas. In both cases a P<sup>-1</sup>u
  132.  * vector and a P<sup>-1</sup> A P matrix are used that do not depend on the state,
  133.  * they only depend on k. This class handles these transformations.</p>
  134.  *
  135.  * @param <T> the type of the field elements
  136.  */
  137. public class AdamsNordsieckFieldTransformer<T extends CalculusFieldElement<T>> {

  138.     /** Cache for already computed coefficients. */
  139.     private static final Map<Integer,
  140.                          Map<Field<? extends CalculusFieldElement<?>>,
  141.                                    AdamsNordsieckFieldTransformer<? extends CalculusFieldElement<?>>>> CACHE = new HashMap<>();

  142.     /** Field to which the time and state vector elements belong. */
  143.     private final Field<T> field;

  144.     /** Update matrix for the higher order derivatives h<sup>2</sup>/2 y'', h<sup>3</sup>/6 y''' ... */
  145.     private final Array2DRowFieldMatrix<T> update;

  146.     /** Update coefficients of the higher order derivatives wrt y'. */
  147.     private final T[] c1;

  148.     /** Simple constructor.
  149.      * @param field field to which the time and state vector elements belong
  150.      * @param n number of steps of the multistep method
  151.      * (excluding the one being computed)
  152.      */
  153.     private AdamsNordsieckFieldTransformer(final Field<T> field, final int n) {

  154.         this.field = field;
  155.         final int rows = n - 1;

  156.         // compute coefficients
  157.         FieldMatrix<T> bigP = buildP(rows);
  158.         FieldDecompositionSolver<T> pSolver =
  159.                 new FieldLUDecomposition<>(bigP).getSolver();

  160.         T[] u = MathArrays.buildArray(field, rows);
  161.         Arrays.fill(u, field.getOne());
  162.         c1 = pSolver.solve(new ArrayFieldVector<>(u, false)).toArray();

  163.         // update coefficients are computed by combining transform from
  164.         // Nordsieck to multistep, then shifting rows to represent step advance
  165.         // then applying inverse transform
  166.         T[][] shiftedP = bigP.getData();
  167.         for (int i = shiftedP.length - 1; i > 0; --i) {
  168.             // shift rows
  169.             shiftedP[i] = shiftedP[i - 1];
  170.         }
  171.         shiftedP[0] = MathArrays.buildArray(field, rows);
  172.         Arrays.fill(shiftedP[0], field.getZero());
  173.         update = new Array2DRowFieldMatrix<>(pSolver.solve(new Array2DRowFieldMatrix<>(shiftedP, false)).getData());

  174.     }

  175.     /** Get the Nordsieck transformer for a given field and number of steps.
  176.      * @param field field to which the time and state vector elements belong
  177.      * @param nSteps number of steps of the multistep method
  178.      * (excluding the one being computed)
  179.      * @return Nordsieck transformer for the specified field and number of steps
  180.      * @param <T> the type of the field elements
  181.      */
  182.     public static <T extends CalculusFieldElement<T>> AdamsNordsieckFieldTransformer<T>
  183.     getInstance(final Field<T> field, final int nSteps) { // NOPMD - PMD false positive
  184.         synchronized(CACHE) {
  185.             Map<Field<? extends CalculusFieldElement<?>>,
  186.                     AdamsNordsieckFieldTransformer<? extends CalculusFieldElement<?>>> map = CACHE.computeIfAbsent(nSteps, k -> new HashMap<>());
  187.             @SuppressWarnings("unchecked")
  188.             AdamsNordsieckFieldTransformer<T> t = (AdamsNordsieckFieldTransformer<T>) map.get(field);
  189.             if (t == null) {
  190.                 t = new AdamsNordsieckFieldTransformer<>(field, nSteps);
  191.                 map.put(field, t);
  192.             }
  193.             return t;

  194.         }
  195.     }

  196.     /** Build the P matrix.
  197.      * <p>The P matrix general terms are shifted \((j+1) (-i)^j\) terms
  198.      * with i being the row number starting from 1 and j being the column
  199.      * number starting from 1:
  200.      * <pre>
  201.      *        [  -2   3   -4    5  ... ]
  202.      *        [  -4  12  -32   80  ... ]
  203.      *   P =  [  -6  27 -108  405  ... ]
  204.      *        [  -8  48 -256 1280  ... ]
  205.      *        [          ...           ]
  206.      * </pre></p>
  207.      * @param rows number of rows of the matrix
  208.      * @return P matrix
  209.      */
  210.     private FieldMatrix<T> buildP(final int rows) {

  211.         final T[][] pData = MathArrays.buildArray(field, rows, rows);

  212.         for (int i = 1; i <= pData.length; ++i) {
  213.             // build the P matrix elements from Taylor series formulas
  214.             final T[] pI = pData[i - 1];
  215.             final int factor = -i;
  216.             T aj = field.getZero().add(factor);
  217.             for (int j = 1; j <= pI.length; ++j) {
  218.                 pI[j - 1] = aj.multiply(j + 1);
  219.                 aj = aj.multiply(factor);
  220.             }
  221.         }

  222.         return new Array2DRowFieldMatrix<>(pData, false);

  223.     }

  224.     /** Initialize the high order scaled derivatives at step start.
  225.      * @param h step size to use for scaling
  226.      * @param t first steps times
  227.      * @param y first steps states
  228.      * @param yDot first steps derivatives
  229.      * @return Nordieck vector at start of first step (h<sup>2</sup>/2 y''<sub>n</sub>,
  230.      * h<sup>3</sup>/6 y'''<sub>n</sub> ... h<sup>k</sup>/k! y<sup>(k)</sup><sub>n</sub>)
  231.      */

  232.     public Array2DRowFieldMatrix<T> initializeHighOrderDerivatives(final T h, final T[] t,
  233.                                                                    final T[][] y,
  234.                                                                    final T[][] yDot) {

  235.         // using Taylor series with di = ti - t0, we get:
  236.         //  y(ti)  - y(t0)  - di y'(t0) =   di^2 / h^2 s2 + ... +   di^k     / h^k sk + O(h^k)
  237.         //  y'(ti) - y'(t0)             = 2 di   / h^2 s2 + ... + k di^(k-1) / h^k sk + O(h^(k-1))
  238.         // we write these relations for i = 1 to i= 1+n/2 as a set of n + 2 linear
  239.         // equations depending on the Nordsieck vector [s2 ... sk rk], so s2 to sk correspond
  240.         // to the appropriately truncated Taylor expansion, and rk is the Taylor remainder.
  241.         // The goal is to have s2 to sk as accurate as possible considering the fact the sum is
  242.         // truncated and we don't want the error terms to be included in s2 ... sk, so we need
  243.         // to solve also for the remainder
  244.         final T[][] a     = MathArrays.buildArray(field, c1.length + 1, c1.length + 1);
  245.         final T[][] b     = MathArrays.buildArray(field, c1.length + 1, y[0].length);
  246.         final T[]   y0    = y[0];
  247.         final T[]   yDot0 = yDot[0];
  248.         for (int i = 1; i < y.length; ++i) {

  249.             final T di    = t[i].subtract(t[0]);
  250.             final T ratio = di.divide(h);
  251.             T dikM1Ohk    = h.reciprocal();

  252.             // linear coefficients of equations
  253.             // y(ti) - y(t0) - di y'(t0) and y'(ti) - y'(t0)
  254.             final T[] aI    = a[2 * i - 2];
  255.             final T[] aDotI = (2 * i - 1) < a.length ? a[2 * i - 1] : null;
  256.             for (int j = 0; j < aI.length; ++j) {
  257.                 dikM1Ohk = dikM1Ohk.multiply(ratio);
  258.                 aI[j]    = di.multiply(dikM1Ohk);
  259.                 if (aDotI != null) {
  260.                     aDotI[j]  = dikM1Ohk.multiply(j + 2);
  261.                 }
  262.             }

  263.             // expected value of the previous equations
  264.             final T[] yI    = y[i];
  265.             final T[] yDotI = yDot[i];
  266.             final T[] bI    = b[2 * i - 2];
  267.             final T[] bDotI = (2 * i - 1) < b.length ? b[2 * i - 1] : null;
  268.             for (int j = 0; j < yI.length; ++j) {
  269.                 bI[j]    = yI[j].subtract(y0[j]).subtract(di.multiply(yDot0[j]));
  270.                 if (bDotI != null) {
  271.                     bDotI[j] = yDotI[j].subtract(yDot0[j]);
  272.                 }
  273.             }

  274.         }

  275.         // solve the linear system to get the best estimate of the Nordsieck vector [s2 ... sk],
  276.         // with the additional terms s(k+1) and c grabbing the parts after the truncated Taylor expansion
  277.         final FieldLUDecomposition<T> decomposition = new FieldLUDecomposition<>(new Array2DRowFieldMatrix<>(a, false));
  278.         final FieldMatrix<T> x = decomposition.getSolver().solve(new Array2DRowFieldMatrix<>(b, false));

  279.         // extract just the Nordsieck vector [s2 ... sk]
  280.         final Array2DRowFieldMatrix<T> truncatedX =
  281.                         new Array2DRowFieldMatrix<>(field, x.getRowDimension() - 1, x.getColumnDimension());
  282.         for (int i = 0; i < truncatedX.getRowDimension(); ++i) {
  283.             for (int j = 0; j < truncatedX.getColumnDimension(); ++j) {
  284.                 truncatedX.setEntry(i, j, x.getEntry(i, j));
  285.             }
  286.         }
  287.         return truncatedX;

  288.     }

  289.     /** Update the high order scaled derivatives for Adams integrators (phase 1).
  290.      * <p>The complete update of high order derivatives has a form similar to:
  291.      * \[
  292.      * r_{n+1} = (s_1(n) - s_1(n+1)) P^{-1} u + P^{-1} A P r_n
  293.      * \]
  294.      * this method computes the P<sup>-1</sup> A P r<sub>n</sub> part.</p>
  295.      * @param highOrder high order scaled derivatives
  296.      * (h<sup>2</sup>/2 y'', ... h<sup>k</sup>/k! y(k))
  297.      * @return updated high order derivatives
  298.      * @see #updateHighOrderDerivativesPhase2(CalculusFieldElement[], CalculusFieldElement[], Array2DRowFieldMatrix)
  299.      */
  300.     public Array2DRowFieldMatrix<T> updateHighOrderDerivativesPhase1(final Array2DRowFieldMatrix<T> highOrder) {
  301.         return update.multiply(highOrder);
  302.     }

  303.     /** Update the high order scaled derivatives Adams integrators (phase 2).
  304.      * <p>The complete update of high order derivatives has a form similar to:
  305.      * \[
  306.      * r_{n+1} = (s_1(n) - s_1(n+1)) P^{-1} u + P^{-1} A P r_n
  307.      * \]
  308.      * this method computes the (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u part.</p>
  309.      * <p>Phase 1 of the update must already have been performed.</p>
  310.      * @param start first order scaled derivatives at step start
  311.      * @param end first order scaled derivatives at step end
  312.      * @param highOrder high order scaled derivatives, will be modified
  313.      * (h<sup>2</sup>/2 y'', ... h<sup>k</sup>/k! y(k))
  314.      * @see #updateHighOrderDerivativesPhase1(Array2DRowFieldMatrix)
  315.      */
  316.     public void updateHighOrderDerivativesPhase2(final T[] start,
  317.                                                  final T[] end,
  318.                                                  final Array2DRowFieldMatrix<T> highOrder) {
  319.         final T[][] data = highOrder.getDataRef();
  320.         for (int i = 0; i < data.length; ++i) {
  321.             final T[] dataI = data[i];
  322.             final T c1I = c1[i];
  323.             for (int j = 0; j < dataI.length; ++j) {
  324.                 dataI[j] = dataI[j].add(c1I.multiply(start[j].subtract(end[j])));
  325.             }
  326.         }
  327.     }

  328. }