VariationalEquation.java

  1. /*
  2.  * Licensed to the Hipparchus project 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 Hipparchus project 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. package org.hipparchus.ode;

  18. import java.lang.reflect.Array;

  19. import org.hipparchus.exception.LocalizedCoreFormats;
  20. import org.hipparchus.exception.MathIllegalArgumentException;
  21. import org.hipparchus.exception.MathIllegalStateException;

  22. /**
  23.  * This class defines a set of {@link SecondaryODE secondary equations} to
  24.  * compute the global Jacobian matrices with respect to the initial state
  25.  * vector and, if any, to some parameters of the primary ODE set.
  26.  * <p>
  27.  * The primary set of ODE for which Jaobian matrices are requested may be:
  28.  * </p>
  29.  * <ul>
  30.  * <li>a full-fledged {@link ODEJacobiansProvider} that computes by itself
  31.  * both the ODE and its local partial derivatives,</li>
  32.  * <li>a simple {@link OrdinaryDifferentialEquation} which must therefore
  33.  * be completed with a finite differences configuration to compute local
  34.  * partial derivatives (so-called internal differentiation).</li>
  35.  * </ul>
  36.  * <p>
  37.  * As the variational equation automatically inserts {@link
  38.  * ExpandableODE#addSecondaryEquations(SecondaryODE) secondary differential
  39.  * equations}, in the {@link ExpandableODE expandable ODE}, data for
  40.  * initial state must also be inserted before integration and matrices
  41.  * result must be extracted after integration. This implies a precise
  42.  * scheduling of the calls to the various methods of this class. The
  43.  * proper scheduling is the following one:
  44.  * </p>
  45.  * <pre>
  46.  *   // set up equations
  47.  *   ODEJacobiansProvider jode       = new MyODE(...);
  48.  *   ExpandableODE        expandable = new Expandable(jode);
  49.  *   VariationalEquation  ve         = new VariationalEquation(expandable, jode);
  50.  *
  51.  *   // set up initial state
  52.  *   ODEState initWithoutDerivatives = new ODEState(t0, y0);
  53.  *   ve.setInitialMainStateJacobian(dYdY0); // only needed if the default identity matrix is not suitable
  54.  *   ve.setInitialParameterJacobian(name, dYdP); // only needed if the default zero matrix is not suitable
  55.  *   ODEState initWithDerivatives = ve.setUpInitialState(initWithoutDerivatives);
  56.  *
  57.  *   // perform integration on the expanded equations with the expanded initial state
  58.  *   ODEStateAndDerivative finalState = integrator.integrate(expandable, initWithDerivatives, finalT);
  59.  *
  60.  *   // extract Jacobian matrices
  61.  *   dYdY0 = ve.extractMainSetJacobian(finalState);
  62.  *   dYdP  = ve.extractParameterJacobian(finalState, name);
  63.  * </pre>
  64.  * <p>
  65.  * The most important part is to not forget to call {@link #setUpInitialState(ODEState)} to add
  66.  * the secondary state with the initial matrices to the {@link ODEState} used in the
  67.  * {@link ODEIntegrator#integrate(ExpandableODE, ODEState, double) integrate} method.
  68.  * Forgetting to do this and passing only a {@link ODEState} without the secondary state
  69.  * set up will trigger an error as the state vector will not have the correct dimension.
  70.  * </p>
  71.  *
  72.  * @see ExpandableODE
  73.  * @see ODEJacobiansProvider
  74.  * @see OrdinaryDifferentialEquation
  75.  * @see NamedParameterJacobianProvider
  76.  * @see ParametersController
  77.  *
  78.  */
  79. public class VariationalEquation {

  80.     /** ODE with Jacobian computation skill. */
  81.     private final ODEJacobiansProvider jode;

  82.     /** Expandable first order differential equation. */
  83.     private final ExpandableODE expandable;

  84.     /** Index of the instance in the expandable set. */
  85.     private final int index;

  86.     /** State and parameters Jacobian matrices in a row. */
  87.     private double[] matricesData;

  88.     /** Build variational equation using finite differences for local
  89.      * partial derivatives.
  90.      * @param expandable expandable set into which variational equations should be registered
  91.      * @param ode base ordinary differential equation for which Jacobians
  92.      * matrices are requested
  93.      * @param hY step used for finite difference computation with respect to state vector
  94.      * @param controller controller to change parameters
  95.      * @param paramsAndSteps parameters and steps to compute the Jacobians df/dp
  96.      * @exception MismatchedEquations if the primary set of the expandable set does
  97.      * not match the {@code ode}
  98.      */
  99.     public VariationalEquation(final ExpandableODE expandable,
  100.                                final OrdinaryDifferentialEquation ode, final double[] hY,
  101.                                final ParametersController controller,
  102.                                final ParameterConfiguration ... paramsAndSteps)
  103.         throws MismatchedEquations {
  104.         this(expandable, new ParameterJacobianWrapper(ode, hY, controller, paramsAndSteps));
  105.     }

  106.     /** Build variational equation using analytical local partial derivatives.
  107.      * <p>
  108.      * Parameters must belong to the supported ones given by {@link
  109.      * Parameterizable#getParametersNames()}, so the primary set of differential
  110.      * equations must be {@link Parameterizable}.
  111.      * </p>
  112.      * <p>Note that each selection clears the previous selected parameters.</p>
  113.      *
  114.      * @param expandable expandable set into which variational equations should be registered
  115.      * @param jode the primary first order differential equations set to extend
  116.      * @exception MismatchedEquations if the primary set of the expandable set does
  117.      * not match the {@code ode}
  118.      */
  119.     public VariationalEquation(final ExpandableODE expandable,
  120.                                final ODEJacobiansProvider jode)
  121.         throws MismatchedEquations {

  122.         // safety checks
  123.         final OrdinaryDifferentialEquation ode;
  124.         if (jode instanceof ParameterJacobianWrapper) {
  125.             ode = ((ParameterJacobianWrapper) jode).getODE();
  126.         } else {
  127.             ode = jode;
  128.         }
  129.         if (expandable.getPrimary() != ode) {
  130.             throw new MismatchedEquations();
  131.         }

  132.         this.jode       = jode;
  133.         this.expandable = expandable;
  134.         this.index      = expandable.addSecondaryEquations(new JacobiansSecondaryODE());

  135.         // set the default initial state Jacobian to the identity
  136.         // and the default initial parameters Jacobian to the null matrix
  137.         matricesData = new double[(jode.getDimension() + jode.getParametersNames().size()) * jode.getDimension()];
  138.         for (int i = 0; i < jode.getDimension(); ++i) {
  139.             matricesData[i * (jode.getDimension() + 1)] = 1.0;
  140.         }

  141.     }

  142.     /** Set the initial value of the Jacobian matrix with respect to state.
  143.      * <p>
  144.      * If this method is not called, the initial value of the Jacobian
  145.      * matrix with respect to state is set to identity.
  146.      * </p>
  147.      * <p>
  148.      * This method must be called <em>before {@link #setUpInitialState(ODEState)}</em>
  149.      * </p>
  150.      * @param dYdY0 initial Jacobian matrix w.r.t. state
  151.      * @exception MathIllegalArgumentException if matrix dimensions are incorrect
  152.      */
  153.     public void setInitialMainStateJacobian(final double[][] dYdY0)
  154.         throws MathIllegalArgumentException {

  155.         // Check dimensions
  156.         checkDimension(jode.getDimension(), dYdY0);
  157.         checkDimension(jode.getDimension(), dYdY0[0]);

  158.         // store the matrix in row major order as a single dimension array
  159.         int i = 0;
  160.         for (final double[] row : dYdY0) {
  161.             System.arraycopy(row, 0, matricesData, i, jode.getDimension());
  162.             i += jode.getDimension();
  163.         }

  164.     }

  165.     /** Set the initial value of a column of the Jacobian matrix with respect to one parameter.
  166.      * <p>
  167.      * If this method is not called for some parameter, the initial value of
  168.      * the column of the Jacobian matrix with respect to this parameter is set to zero.
  169.      * </p>
  170.      * <p>
  171.      * This method must be called <em>before {@link #setUpInitialState(ODEState)}</em>
  172.      * </p>
  173.      * @param pName parameter name
  174.      * @param dYdP initial Jacobian column vector with respect to the parameter
  175.      * @exception MathIllegalArgumentException if a parameter is not supported
  176.      * @throws MathIllegalArgumentException if the column vector does not match state dimension
  177.      */
  178.     public void setInitialParameterJacobian(final String pName, final double[] dYdP)
  179.         throws MathIllegalArgumentException {

  180.         // Check dimensions
  181.         checkDimension(jode.getDimension(), dYdP);

  182.         // store the column in a global single dimension array
  183.         int i = jode.getDimension() * jode.getDimension();
  184.         for (final String knownParameter : jode.getParametersNames()) {
  185.             if (pName.equals(knownParameter)) {
  186.                 System.arraycopy(dYdP, 0, matricesData, i, jode.getDimension());
  187.                 return;
  188.             }
  189.             i += jode.getDimension();
  190.         }

  191.         throw new MathIllegalArgumentException(LocalizedODEFormats.UNKNOWN_PARAMETER, pName);

  192.     }

  193.     /** Set up initial state.
  194.      * <p>
  195.      * This method inserts the initial Jacobian matrices data into
  196.      * an {@link ODEState ODE state} by overriding the additional
  197.      * state components corresponding to the instance. It must be
  198.      * called prior to integrate the equations.
  199.      * </p>
  200.      * <p>
  201.      * This method must be called <em>after</em>
  202.      * {@link #setInitialMainStateJacobian(double[][])} and
  203.      * {@link #setInitialParameterJacobian(String, double[])}.
  204.      * </p>
  205.      * @param initialState initial state, without the initial Jacobians
  206.      * matrices
  207.      * @return a new instance of initial state, with the initial Jacobians
  208.      * matrices properly initialized
  209.      */
  210.     public ODEState setUpInitialState(final ODEState initialState) { // NOPMD - PMD false positive

  211.         // insert the matrices data into secondary states
  212.         final double[][] secondary = new double[expandable.getMapper().getNumberOfEquations() - 1][];
  213.         for (int i = 0; i < initialState.getNumberOfSecondaryStates(); ++i) {
  214.             if (i + 1 != index) {
  215.                 secondary[i] = initialState.getSecondaryState(i + 1);
  216.             }
  217.         }
  218.         secondary[index - 1] = matricesData;

  219.         // create an updated initial state
  220.         return new ODEState(initialState.getTime(), initialState.getPrimaryState(), secondary);

  221.     }

  222.     /** Extract the Jacobian matrix with respect to state.
  223.      * @param state state from which to extract Jacobian matrix
  224.      * @return Jacobian matrix dY/dY0 with respect to state.
  225.      */
  226.     public double[][] extractMainSetJacobian(final ODEState state) {

  227.         // get current state for this set of equations from the expandable fode
  228.         final double[] p = state.getSecondaryState(index);

  229.         final double[][] dYdY0 = new double[jode.getDimension()][jode.getDimension()];
  230.         int j = 0;
  231.         for (int i = 0; i < jode.getDimension(); i++) {
  232.             System.arraycopy(p, j, dYdY0[i], 0, jode.getDimension());
  233.             j += jode.getDimension();
  234.         }

  235.         return dYdY0;

  236.     }

  237.     /** Extract the Jacobian matrix with respect to one parameter.
  238.      * @param state state from which to extract Jacobian matrix
  239.      * @param pName name of the parameter for the computed Jacobian matrix
  240.      * @return Jacobian matrix dY/dP with respect to the named parameter
  241.      */
  242.     public double[] extractParameterJacobian(final ODEState state, final String pName) {

  243.         // get current state for this set of equations from the expandable fode
  244.         final double[] p = state.getSecondaryState(index);

  245.         final double[] dYdP = new double[jode.getDimension()];
  246.         int i = jode.getDimension() * jode.getDimension();
  247.         for (final String knownParameter : jode.getParametersNames()) {
  248.             if (pName.equals(knownParameter)) {
  249.                 System.arraycopy(p, i, dYdP, 0, jode.getDimension());
  250.                 break;
  251.             }
  252.             i += jode.getDimension();
  253.         }

  254.         return dYdP;

  255.     }

  256.     /** Check array dimensions.
  257.      * @param expected expected dimension
  258.      * @param array (may be null if expected is 0)
  259.      * @throws MathIllegalArgumentException if the array dimension does not match the expected one
  260.      */
  261.     private void checkDimension(final int expected, final Object array)
  262.         throws MathIllegalArgumentException {
  263.         int arrayDimension = (array == null) ? 0 : Array.getLength(array);
  264.         if (arrayDimension != expected) {
  265.             throw new MathIllegalArgumentException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
  266.                                                    arrayDimension, expected);
  267.         }
  268.     }

  269.     /** Local implementation of secondary equations. */
  270.     private class JacobiansSecondaryODE implements SecondaryODE {

  271.         /** {@inheritDoc} */
  272.         @Override
  273.         public int getDimension() {
  274.             return jode.getDimension() * (jode.getDimension() + jode.getParametersNames().size());
  275.         }

  276.         /** {@inheritDoc} */
  277.         @Override
  278.         public double[] computeDerivatives(final double t, final double[] y, final double[] yDot,
  279.                                            final double[] z)
  280.             throws MathIllegalArgumentException, MathIllegalStateException {

  281.             final double[] zDot = new double[z.length];

  282.             // variational equations:
  283.             // from d[dy/dt]/dy0 and d[dy/dt]/dp to d[dy/dy0]/dt and d[dy/dp]/dt

  284.             // compute Jacobian matrix with respect to primary state
  285.             double[][] dFdY = jode.computeMainStateJacobian(t, y, yDot);

  286.             // Dispatch Jacobian matrix in the compound secondary state vector
  287.             for (int i = 0; i < jode.getDimension(); ++i) {
  288.                 final double[] dFdYi = dFdY[i];
  289.                 for (int j = 0; j < jode.getDimension(); ++j) {
  290.                     double s = 0;
  291.                     final int startIndex = j;
  292.                     int zIndex = startIndex;
  293.                     for (int l = 0; l < jode.getDimension(); ++l) {
  294.                         s += dFdYi[l] * z[zIndex];
  295.                         zIndex += jode.getDimension();
  296.                     }
  297.                     zDot[startIndex + i * jode.getDimension()] = s;
  298.                 }
  299.             }

  300.             // compute Jacobian matrices with respect to parameters
  301.             int startIndex = jode.getDimension() * jode.getDimension();
  302.             for (final String name : jode.getParametersNames()) {
  303.                 final double[] dFdP = jode.computeParameterJacobian(t, y, yDot, name);
  304.                 for (int i = 0; i < jode.getDimension(); ++i) {
  305.                     final double[] dFdYi = dFdY[i];
  306.                     int zIndex = startIndex;
  307.                     double s = dFdP[i];
  308.                     for (int l = 0; l < jode.getDimension(); ++l) {
  309.                         s += dFdYi[l] * z[zIndex];
  310.                         zIndex++;
  311.                     }
  312.                     zDot[startIndex + i] = s;
  313.                 }
  314.                 startIndex += jode.getDimension();
  315.             }

  316.             return zDot;

  317.         }
  318.     }

  319.     /**
  320.      * Special exception for equations mismatch.
  321.      */
  322.     public static class MismatchedEquations extends MathIllegalArgumentException {

  323.         /** Serializable UID. */
  324.         private static final long serialVersionUID = 20120902L;

  325.         /** Simple constructor. */
  326.         public MismatchedEquations() {
  327.             super(LocalizedODEFormats.UNMATCHED_ODE_IN_EXPANDED_SET);
  328.         }

  329.     }

  330. }