JacobianMatrices.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.  *      http://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.apache.commons.math4.legacy.ode;

  18. import java.lang.reflect.Array;
  19. import java.util.ArrayList;
  20. import java.util.Arrays;
  21. import java.util.List;

  22. import org.apache.commons.math4.legacy.exception.DimensionMismatchException;
  23. import org.apache.commons.math4.legacy.exception.MathIllegalArgumentException;
  24. import org.apache.commons.math4.legacy.exception.MaxCountExceededException;
  25. import org.apache.commons.math4.legacy.exception.util.LocalizedFormats;

  26. /**
  27.  * This class defines a set of {@link SecondaryEquations secondary equations} to
  28.  * compute the Jacobian matrices with respect to the initial state vector and, if
  29.  * any, to some parameters of the primary ODE set.
  30.  * <p>
  31.  * It is intended to be packed into an {@link ExpandableStatefulODE}
  32.  * in conjunction with a primary set of ODE, which may be:
  33.  * <ul>
  34.  * <li>a {@link FirstOrderDifferentialEquations}</li>
  35.  * <li>a {@link MainStateJacobianProvider}</li>
  36.  * </ul>
  37.  * In order to compute Jacobian matrices with respect to some parameters of the
  38.  * primary ODE set, the following parameter Jacobian providers may be set:
  39.  * <ul>
  40.  * <li>a {@link ParameterJacobianProvider}</li>
  41.  * <li>a {@link ParameterizedODE}</li>
  42.  * </ul>
  43.  *
  44.  * @see ExpandableStatefulODE
  45.  * @see FirstOrderDifferentialEquations
  46.  * @see MainStateJacobianProvider
  47.  * @see ParameterJacobianProvider
  48.  * @see ParameterizedODE
  49.  *
  50.  * @since 3.0
  51.  */
  52. public class JacobianMatrices {

  53.     /** Expandable first order differential equation. */
  54.     private ExpandableStatefulODE efode;

  55.     /** Index of the instance in the expandable set. */
  56.     private int index;

  57.     /** FODE with exact primary Jacobian computation skill. */
  58.     private MainStateJacobianProvider jode;

  59.     /** FODE without exact parameter Jacobian computation skill. */
  60.     private ParameterizedODE pode;

  61.     /** Main state vector dimension. */
  62.     private int stateDim;

  63.     /** Selected parameters for parameter Jacobian computation. */
  64.     private ParameterConfiguration[] selectedParameters;

  65.     /** FODE with exact parameter Jacobian computation skill. */
  66.     private List<ParameterJacobianProvider> jacobianProviders;

  67.     /** Parameters dimension. */
  68.     private int paramDim;

  69.     /** Boolean for selected parameters consistency. */
  70.     private boolean dirtyParameter;

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

  73.     /** Simple constructor for a secondary equations set computing Jacobian matrices.
  74.      * <p>
  75.      * Parameters must belong to the supported ones given by {@link
  76.      * Parameterizable#getParametersNames()}, so the primary set of differential
  77.      * equations must be {@link Parameterizable}.
  78.      * </p>
  79.      * <p>Note that each selection clears the previous selected parameters.</p>
  80.      *
  81.      * @param fode the primary first order differential equations set to extend
  82.      * @param hY step used for finite difference computation with respect to state vector
  83.      * @param parameters parameters to consider for Jacobian matrices processing
  84.      * (may be null if parameters Jacobians is not desired)
  85.      * @exception DimensionMismatchException if there is a dimension mismatch between
  86.      * the steps array {@code hY} and the equation dimension
  87.      */
  88.     public JacobianMatrices(final FirstOrderDifferentialEquations fode, final double[] hY,
  89.                             final String... parameters)
  90.         throws DimensionMismatchException {
  91.         this(new MainStateJacobianWrapper(fode, hY), parameters);
  92.     }

  93.     /** Simple constructor for a secondary equations set computing Jacobian matrices.
  94.      * <p>
  95.      * Parameters must belong to the supported ones given by {@link
  96.      * Parameterizable#getParametersNames()}, so the primary set of differential
  97.      * equations must be {@link Parameterizable}.
  98.      * </p>
  99.      * <p>Note that each selection clears the previous selected parameters.</p>
  100.      *
  101.      * @param jode the primary first order differential equations set to extend
  102.      * @param parameters parameters to consider for Jacobian matrices processing
  103.      * (may be null if parameters Jacobians is not desired)
  104.      */
  105.     public JacobianMatrices(final MainStateJacobianProvider jode,
  106.                             final String... parameters) {

  107.         this.efode = null;
  108.         this.index = -1;

  109.         this.jode = jode;
  110.         this.pode = null;

  111.         this.stateDim = jode.getDimension();

  112.         if (parameters == null) {
  113.             selectedParameters = null;
  114.             paramDim = 0;
  115.         } else {
  116.             this.selectedParameters = new ParameterConfiguration[parameters.length];
  117.             for (int i = 0; i < parameters.length; ++i) {
  118.                 selectedParameters[i] = new ParameterConfiguration(parameters[i], Double.NaN);
  119.             }
  120.             paramDim = parameters.length;
  121.         }
  122.         this.dirtyParameter = false;

  123.         this.jacobianProviders = new ArrayList<>();

  124.         // set the default initial state Jacobian to the identity
  125.         // and the default initial parameters Jacobian to the null matrix
  126.         matricesData = new double[(stateDim + paramDim) * stateDim];
  127.         for (int i = 0; i < stateDim; ++i) {
  128.             matricesData[i * (stateDim + 1)] = 1.0;
  129.         }
  130.     }

  131.     /** Register the variational equations for the Jacobians matrices to the expandable set.
  132.      * @param expandable expandable set into which variational equations should be registered
  133.      * @throws DimensionMismatchException if the dimension of the partial state does not
  134.      * match the selected equations set dimension
  135.      * @exception MismatchedEquations if the primary set of the expandable set does
  136.      * not match the one used to build the instance
  137.      * @see ExpandableStatefulODE#addSecondaryEquations(SecondaryEquations)
  138.      */
  139.     public void registerVariationalEquations(final ExpandableStatefulODE expandable)
  140.         throws DimensionMismatchException, MismatchedEquations {

  141.         // safety checks
  142.         final FirstOrderDifferentialEquations ode = (jode instanceof MainStateJacobianWrapper) ?
  143.                                                     ((MainStateJacobianWrapper) jode).ode :
  144.                                                     jode;
  145.         if (expandable.getPrimary() != ode) {
  146.             throw new MismatchedEquations();
  147.         }

  148.         efode = expandable;
  149.         index = efode.addSecondaryEquations(new JacobiansSecondaryEquations());
  150.         efode.setSecondaryState(index, matricesData);
  151.     }

  152.     /** Add a parameter Jacobian provider.
  153.      * @param provider the parameter Jacobian provider to compute exactly the parameter Jacobian matrix
  154.      */
  155.     public void addParameterJacobianProvider(final ParameterJacobianProvider provider) {
  156.         jacobianProviders.add(provider);
  157.     }

  158.     /** Set a parameter Jacobian provider.
  159.      * @param parameterizedOde the parameterized ODE to compute the parameter Jacobian matrix using finite differences
  160.      */
  161.     public void setParameterizedODE(final ParameterizedODE parameterizedOde) {
  162.         this.pode = parameterizedOde;
  163.         dirtyParameter = true;
  164.     }

  165.     /** Set the step associated to a parameter in order to compute by finite
  166.      *  difference the Jacobian matrix.
  167.      * <p>
  168.      * Needed if and only if the primary ODE set is a {@link ParameterizedODE}.
  169.      * </p>
  170.      * <p>
  171.      * Given a non zero parameter value pval for the parameter, a reasonable value
  172.      * for such a step is {@code pval * JdkMath.sqrt(Precision.EPSILON)}.
  173.      * </p>
  174.      * <p>
  175.      * A zero value for such a step doesn't enable to compute the parameter Jacobian matrix.
  176.      * </p>
  177.      * @param parameter parameter to consider for Jacobian processing
  178.      * @param hP step for Jacobian finite difference computation w.r.t. the specified parameter
  179.      * @see ParameterizedODE
  180.      * @exception UnknownParameterException if the parameter is not supported
  181.      */
  182.     public void setParameterStep(final String parameter, final double hP)
  183.         throws UnknownParameterException {

  184.         for (ParameterConfiguration param: selectedParameters) {
  185.             if (parameter.equals(param.getParameterName())) {
  186.                 param.setHP(hP);
  187.                 dirtyParameter = true;
  188.                 return;
  189.             }
  190.         }

  191.         throw new UnknownParameterException(parameter);
  192.     }

  193.     /** Set the initial value of the Jacobian matrix with respect to state.
  194.      * <p>
  195.      * If this method is not called, the initial value of the Jacobian
  196.      * matrix with respect to state is set to identity.
  197.      * </p>
  198.      * @param dYdY0 initial Jacobian matrix w.r.t. state
  199.      * @exception DimensionMismatchException if matrix dimensions are incorrect
  200.      */
  201.     public void setInitialMainStateJacobian(final double[][] dYdY0)
  202.         throws DimensionMismatchException {

  203.         // Check dimensions
  204.         checkDimension(stateDim, dYdY0);
  205.         checkDimension(stateDim, dYdY0[0]);

  206.         // store the matrix in row major order as a single dimension array
  207.         int i = 0;
  208.         for (final double[] row : dYdY0) {
  209.             System.arraycopy(row, 0, matricesData, i, stateDim);
  210.             i += stateDim;
  211.         }

  212.         if (efode != null) {
  213.             efode.setSecondaryState(index, matricesData);
  214.         }
  215.     }

  216.     /** Set the initial value of a column of the Jacobian matrix with respect to one parameter.
  217.      * <p>
  218.      * If this method is not called for some parameter, the initial value of
  219.      * the column of the Jacobian matrix with respect to this parameter is set to zero.
  220.      * </p>
  221.      * @param pName parameter name
  222.      * @param dYdP initial Jacobian column vector with respect to the parameter
  223.      * @exception UnknownParameterException if a parameter is not supported
  224.      * @throws DimensionMismatchException if the column vector does not match state dimension
  225.      */
  226.     public void setInitialParameterJacobian(final String pName, final double[] dYdP)
  227.         throws UnknownParameterException, DimensionMismatchException {

  228.         // Check dimensions
  229.         checkDimension(stateDim, dYdP);

  230.         // store the column in a global single dimension array
  231.         int i = stateDim * stateDim;
  232.         for (ParameterConfiguration param: selectedParameters) {
  233.             if (pName.equals(param.getParameterName())) {
  234.                 System.arraycopy(dYdP, 0, matricesData, i, stateDim);
  235.                 if (efode != null) {
  236.                     efode.setSecondaryState(index, matricesData);
  237.                 }
  238.                 return;
  239.             }
  240.             i += stateDim;
  241.         }

  242.         throw new UnknownParameterException(pName);
  243.     }

  244.     /** Get the current value of the Jacobian matrix with respect to state.
  245.      * @param dYdY0 current Jacobian matrix with respect to state.
  246.      */
  247.     public void getCurrentMainSetJacobian(final double[][] dYdY0) {

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

  250.         int j = 0;
  251.         for (int i = 0; i < stateDim; i++) {
  252.             System.arraycopy(p, j, dYdY0[i], 0, stateDim);
  253.             j += stateDim;
  254.         }
  255.     }

  256.     /** Get the current value of the Jacobian matrix with respect to one parameter.
  257.      * @param pName name of the parameter for the computed Jacobian matrix
  258.      * @param dYdP current Jacobian matrix with respect to the named parameter
  259.      */
  260.     public void getCurrentParameterJacobian(String pName, final double[] dYdP) {

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

  263.         int i = stateDim * stateDim;
  264.         for (ParameterConfiguration param: selectedParameters) {
  265.             if (param.getParameterName().equals(pName)) {
  266.                 System.arraycopy(p, i, dYdP, 0, stateDim);
  267.                 return;
  268.             }
  269.             i += stateDim;
  270.         }
  271.     }

  272.     /** Check array dimensions.
  273.      * @param expected expected dimension
  274.      * @param array (may be null if expected is 0)
  275.      * @throws DimensionMismatchException if the array dimension does not match the expected one
  276.      */
  277.     private void checkDimension(final int expected, final Object array)
  278.         throws DimensionMismatchException {
  279.         int arrayDimension = (array == null) ? 0 : Array.getLength(array);
  280.         if (arrayDimension != expected) {
  281.             throw new DimensionMismatchException(arrayDimension, expected);
  282.         }
  283.     }

  284.     /** Local implementation of secondary equations.
  285.      * <p>
  286.      * This class is an inner class to ensure proper scheduling of calls
  287.      * by forcing the use of {@link JacobianMatrices#registerVariationalEquations(ExpandableStatefulODE)}.
  288.      * </p>
  289.      */
  290.     private final class JacobiansSecondaryEquations implements SecondaryEquations {

  291.         /** {@inheritDoc} */
  292.         @Override
  293.         public int getDimension() {
  294.             return stateDim * (stateDim + paramDim);
  295.         }

  296.         /** {@inheritDoc} */
  297.         @Override
  298.         public void computeDerivatives(final double t, final double[] y, final double[] yDot,
  299.                                        final double[] z, final double[] zDot)
  300.             throws MaxCountExceededException, DimensionMismatchException {

  301.             // Lazy initialization
  302.             if (dirtyParameter && paramDim != 0) {
  303.                 jacobianProviders.add(new ParameterJacobianWrapper(jode, pode, selectedParameters));
  304.                 dirtyParameter = false;
  305.             }

  306.             // variational equations:
  307.             // from d[dy/dt]/dy0 and d[dy/dt]/dp to d[dy/dy0]/dt and d[dy/dp]/dt

  308.             // compute Jacobian matrix with respect to primary state
  309.             double[][] dFdY = new double[stateDim][stateDim];
  310.             jode.computeMainStateJacobian(t, y, yDot, dFdY);

  311.             // Dispatch Jacobian matrix in the compound secondary state vector
  312.             for (int i = 0; i < stateDim; ++i) {
  313.                 final double[] dFdYi = dFdY[i];
  314.                 for (int j = 0; j < stateDim; ++j) {
  315.                     double s = 0;
  316.                     final int startIndex = j;
  317.                     int zIndex = startIndex;
  318.                     for (int l = 0; l < stateDim; ++l) {
  319.                         s += dFdYi[l] * z[zIndex];
  320.                         zIndex += stateDim;
  321.                     }
  322.                     zDot[startIndex + i * stateDim] = s;
  323.                 }
  324.             }

  325.             if (paramDim != 0) {
  326.                 // compute Jacobian matrices with respect to parameters
  327.                 double[] dFdP = new double[stateDim];
  328.                 int startIndex = stateDim * stateDim;
  329.                 for (ParameterConfiguration param: selectedParameters) {
  330.                     boolean found = false;
  331.                     for (int k = 0 ; !found && k < jacobianProviders.size(); ++k) {
  332.                         final ParameterJacobianProvider provider = jacobianProviders.get(k);
  333.                         if (provider.isSupported(param.getParameterName())) {
  334.                             provider.computeParameterJacobian(t, y, yDot,
  335.                                                               param.getParameterName(), dFdP);
  336.                             for (int i = 0; i < stateDim; ++i) {
  337.                                 final double[] dFdYi = dFdY[i];
  338.                                 int zIndex = startIndex;
  339.                                 double s = dFdP[i];
  340.                                 for (int l = 0; l < stateDim; ++l) {
  341.                                     s += dFdYi[l] * z[zIndex];
  342.                                     zIndex++;
  343.                                 }
  344.                                 zDot[startIndex + i] = s;
  345.                             }
  346.                             found = true;
  347.                         }
  348.                     }
  349.                     if (! found) {
  350.                         Arrays.fill(zDot, startIndex, startIndex + stateDim, 0.0);
  351.                     }
  352.                     startIndex += stateDim;
  353.                 }
  354.             }
  355.         }
  356.     }

  357.     /** Wrapper class to compute jacobian matrices by finite differences for ODE
  358.      *  which do not compute them by themselves.
  359.      */
  360.     private static final class MainStateJacobianWrapper implements MainStateJacobianProvider {

  361.         /** Raw ODE without jacobians computation skill to be wrapped into a MainStateJacobianProvider. */
  362.         private final FirstOrderDifferentialEquations ode;

  363.         /** Steps for finite difference computation of the jacobian df/dy w.r.t. state. */
  364.         private final double[] hY;

  365.         /** Wrap a {@link FirstOrderDifferentialEquations} into a {@link MainStateJacobianProvider}.
  366.          * @param ode original ODE problem, without jacobians computation skill
  367.          * @param hY step sizes to compute the jacobian df/dy
  368.          * @exception DimensionMismatchException if there is a dimension mismatch between
  369.          * the steps array {@code hY} and the equation dimension
  370.          */
  371.         MainStateJacobianWrapper(final FirstOrderDifferentialEquations ode,
  372.                                  final double[] hY)
  373.             throws DimensionMismatchException {
  374.             this.ode = ode;
  375.             this.hY = hY.clone();
  376.             if (hY.length != ode.getDimension()) {
  377.                 throw new DimensionMismatchException(ode.getDimension(), hY.length);
  378.             }
  379.         }

  380.         /** {@inheritDoc} */
  381.         @Override
  382.         public int getDimension() {
  383.             return ode.getDimension();
  384.         }

  385.         /** {@inheritDoc} */
  386.         @Override
  387.         public void computeDerivatives(double t, double[] y, double[] yDot)
  388.             throws MaxCountExceededException, DimensionMismatchException {
  389.             ode.computeDerivatives(t, y, yDot);
  390.         }

  391.         /** {@inheritDoc} */
  392.         @Override
  393.         public void computeMainStateJacobian(double t, double[] y, double[] yDot, double[][] dFdY)
  394.             throws MaxCountExceededException, DimensionMismatchException {

  395.             final int n = ode.getDimension();
  396.             final double[] tmpDot = new double[n];

  397.             for (int j = 0; j < n; ++j) {
  398.                 final double savedYj = y[j];
  399.                 y[j] += hY[j];
  400.                 ode.computeDerivatives(t, y, tmpDot);
  401.                 for (int i = 0; i < n; ++i) {
  402.                     dFdY[i][j] = (tmpDot[i] - yDot[i]) / hY[j];
  403.                 }
  404.                 y[j] = savedYj;
  405.             }
  406.         }
  407.     }

  408.     /**
  409.      * Special exception for equations mismatch.
  410.      * @since 3.1
  411.      */
  412.     public static class MismatchedEquations extends MathIllegalArgumentException {

  413.         /** Serializable UID. */
  414.         private static final long serialVersionUID = 20120902L;

  415.         /** Simple constructor. */
  416.         public MismatchedEquations() {
  417.             super(LocalizedFormats.UNMATCHED_ODE_IN_EXPANDED_SET);
  418.         }
  419.     }
  420. }