View Javadoc
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  
19  import java.lang.reflect.Array;
20  
21  import org.hipparchus.exception.LocalizedCoreFormats;
22  import org.hipparchus.exception.MathIllegalArgumentException;
23  import org.hipparchus.exception.MathIllegalStateException;
24  
25  /**
26   * This class defines a set of {@link SecondaryODE secondary equations} to
27   * compute the global Jacobian matrices with respect to the initial state
28   * vector and, if any, to some parameters of the primary ODE set.
29   * <p>
30   * The primary set of ODE for which Jaobian matrices are requested may be:
31   * </p>
32   * <ul>
33   * <li>a full-fledged {@link ODEJacobiansProvider} that computes by itself
34   * both the ODE and its local partial derivatives,</li>
35   * <li>a simple {@link OrdinaryDifferentialEquation} which must therefore
36   * be completed with a finite differences configuration to compute local
37   * partial derivatives (so-called internal differentiation).</li>
38   * </ul>
39   * <p>
40   * As the variational equation automatically inserts {@link
41   * ExpandableODE#addSecondaryEquations(SecondaryODE) secondary differential
42   * equations}, in the {@link ExpandableODE expandable ODE}, data for
43   * initial state must also be inserted before integration and matrices
44   * result must be extracted after integration. This implies a precise
45   * scheduling of the calls to the various methods of this class. The
46   * proper scheduling is the following one:
47   * </p>
48   * <pre>
49   *   // set up equations
50   *   ODEJacobiansProvider jode       = new MyODE(...);
51   *   ExpandableODE        expandable = new Expandable(jode);
52   *   VariationalEquation  ve         = new VariationalEquation(expandable, jode);
53   *
54   *   // set up initial state
55   *   ODEState initWithoutDerivatives = new ODEState(t0, y0);
56   *   ve.setInitialMainStateJacobian(dYdY0); // only needed if the default identity matrix is not suitable
57   *   ve.setInitialParameterJacobian(name, dYdP); // only needed if the default zero matrix is not suitable
58   *   ODEState initWithDerivatives = ve.setUpInitialState(initWithoutDerivatives);
59   *
60   *   // perform integration on the expanded equations with the expanded initial state
61   *   ODEStateAndDerivative finalState = integrator.integrate(expandable, initWithDerivatives, finalT);
62   *
63   *   // extract Jacobian matrices
64   *   dYdY0 = ve.extractMainSetJacobian(finalState);
65   *   dYdP  = ve.extractParameterJacobian(finalState, name);
66   * </pre>
67   * <p>
68   * The most important part is to not forget to call {@link #setUpInitialState(ODEState)} to add
69   * the secondary state with the initial matrices to the {@link ODEState} used in the
70   * {@link ODEIntegrator#integrate(ExpandableODE, ODEState, double) integrate} method.
71   * Forgetting to do this and passing only a {@link ODEState} without the secondary state
72   * set up will trigger an error as the state vector will not have the correct dimension.
73   * </p>
74   *
75   * @see ExpandableODE
76   * @see ODEJacobiansProvider
77   * @see OrdinaryDifferentialEquation
78   * @see NamedParameterJacobianProvider
79   * @see ParametersController
80   *
81   */
82  public class VariationalEquation {
83  
84      /** ODE with Jacobian computation skill. */
85      private final ODEJacobiansProvider jode;
86  
87      /** Expandable first order differential equation. */
88      private final ExpandableODE expandable;
89  
90      /** Index of the instance in the expandable set. */
91      private final int index;
92  
93      /** State and parameters Jacobian matrices in a row. */
94      private double[] matricesData;
95  
96      /** Build variational equation using finite differences for local
97       * partial derivatives.
98       * @param expandable expandable set into which variational equations should be registered
99       * @param ode base ordinary differential equation for which Jacobians
100      * matrices are requested
101      * @param hY step used for finite difference computation with respect to state vector
102      * @param controller controller to change parameters
103      * @param paramsAndSteps parameters and steps to compute the Jacobians df/dp
104      * @exception MismatchedEquations if the primary set of the expandable set does
105      * not match the {@code ode}
106      */
107     public VariationalEquation(final ExpandableODE expandable,
108                                final OrdinaryDifferentialEquation ode, final double[] hY,
109                                final ParametersController controller,
110                                final ParameterConfiguration ... paramsAndSteps)
111         throws MismatchedEquations {
112         this(expandable, new ParameterJacobianWrapper(ode, hY, controller, paramsAndSteps));
113     }
114 
115     /** Build variational equation using analytical local partial derivatives.
116      * <p>
117      * Parameters must belong to the supported ones given by {@link
118      * Parameterizable#getParametersNames()}, so the primary set of differential
119      * equations must be {@link Parameterizable}.
120      * </p>
121      * <p>Note that each selection clears the previous selected parameters.</p>
122      *
123      * @param expandable expandable set into which variational equations should be registered
124      * @param jode the primary first order differential equations set to extend
125      * @exception MismatchedEquations if the primary set of the expandable set does
126      * not match the {@code ode}
127      */
128     public VariationalEquation(final ExpandableODE expandable,
129                                final ODEJacobiansProvider jode)
130         throws MismatchedEquations {
131 
132         // safety checks
133         final OrdinaryDifferentialEquation ode;
134         if (jode instanceof ParameterJacobianWrapper) {
135             ode = ((ParameterJacobianWrapper) jode).getODE();
136         } else {
137             ode = jode;
138         }
139         if (expandable.getPrimary() != ode) {
140             throw new MismatchedEquations();
141         }
142 
143         this.jode       = jode;
144         this.expandable = expandable;
145         this.index      = expandable.addSecondaryEquations(new JacobiansSecondaryODE());
146 
147         // set the default initial state Jacobian to the identity
148         // and the default initial parameters Jacobian to the null matrix
149         matricesData = new double[(jode.getDimension() + jode.getParametersNames().size()) * jode.getDimension()];
150         for (int i = 0; i < jode.getDimension(); ++i) {
151             matricesData[i * (jode.getDimension() + 1)] = 1.0;
152         }
153 
154     }
155 
156     /** Set the initial value of the Jacobian matrix with respect to state.
157      * <p>
158      * If this method is not called, the initial value of the Jacobian
159      * matrix with respect to state is set to identity.
160      * </p>
161      * <p>
162      * This method must be called <em>before {@link #setUpInitialState(ODEState)}</em>
163      * </p>
164      * @param dYdY0 initial Jacobian matrix w.r.t. state
165      * @exception MathIllegalArgumentException if matrix dimensions are incorrect
166      */
167     public void setInitialMainStateJacobian(final double[][] dYdY0)
168         throws MathIllegalArgumentException {
169 
170         // Check dimensions
171         checkDimension(jode.getDimension(), dYdY0);
172         checkDimension(jode.getDimension(), dYdY0[0]);
173 
174         // store the matrix in row major order as a single dimension array
175         int i = 0;
176         for (final double[] row : dYdY0) {
177             System.arraycopy(row, 0, matricesData, i, jode.getDimension());
178             i += jode.getDimension();
179         }
180 
181     }
182 
183     /** Set the initial value of a column of the Jacobian matrix with respect to one parameter.
184      * <p>
185      * If this method is not called for some parameter, the initial value of
186      * the column of the Jacobian matrix with respect to this parameter is set to zero.
187      * </p>
188      * <p>
189      * This method must be called <em>before {@link #setUpInitialState(ODEState)}</em>
190      * </p>
191      * @param pName parameter name
192      * @param dYdP initial Jacobian column vector with respect to the parameter
193      * @exception MathIllegalArgumentException if a parameter is not supported
194      * @throws MathIllegalArgumentException if the column vector does not match state dimension
195      */
196     public void setInitialParameterJacobian(final String pName, final double[] dYdP)
197         throws MathIllegalArgumentException {
198 
199         // Check dimensions
200         checkDimension(jode.getDimension(), dYdP);
201 
202         // store the column in a global single dimension array
203         int i = jode.getDimension() * jode.getDimension();
204         for (final String knownParameter : jode.getParametersNames()) {
205             if (pName.equals(knownParameter)) {
206                 System.arraycopy(dYdP, 0, matricesData, i, jode.getDimension());
207                 return;
208             }
209             i += jode.getDimension();
210         }
211 
212         throw new MathIllegalArgumentException(LocalizedODEFormats.UNKNOWN_PARAMETER, pName);
213 
214     }
215 
216     /** Set up initial state.
217      * <p>
218      * This method inserts the initial Jacobian matrices data into
219      * an {@link ODEState ODE state} by overriding the additional
220      * state components corresponding to the instance. It must be
221      * called prior to integrate the equations.
222      * </p>
223      * <p>
224      * This method must be called <em>after</em>
225      * {@link #setInitialMainStateJacobian(double[][])} and
226      * {@link #setInitialParameterJacobian(String, double[])}.
227      * </p>
228      * @param initialState initial state, without the initial Jacobians
229      * matrices
230      * @return a new instance of initial state, with the initial Jacobians
231      * matrices properly initialized
232      */
233     public ODEState setUpInitialState(final ODEState initialState) { // NOPMD - PMD false positive
234 
235         // insert the matrices data into secondary states
236         final double[][] secondary = new double[expandable.getMapper().getNumberOfEquations() - 1][];
237         for (int i = 0; i < initialState.getNumberOfSecondaryStates(); ++i) {
238             if (i + 1 != index) {
239                 secondary[i] = initialState.getSecondaryState(i + 1);
240             }
241         }
242         secondary[index - 1] = matricesData;
243 
244         // create an updated initial state
245         return new ODEState(initialState.getTime(), initialState.getPrimaryState(), secondary);
246 
247     }
248 
249     /** Extract the Jacobian matrix with respect to state.
250      * @param state state from which to extract Jacobian matrix
251      * @return Jacobian matrix dY/dY0 with respect to state.
252      */
253     public double[][] extractMainSetJacobian(final ODEState state) {
254 
255         // get current state for this set of equations from the expandable fode
256         final double[] p = state.getSecondaryState(index);
257 
258         final double[][] dYdY0 = new double[jode.getDimension()][jode.getDimension()];
259         int j = 0;
260         for (int i = 0; i < jode.getDimension(); i++) {
261             System.arraycopy(p, j, dYdY0[i], 0, jode.getDimension());
262             j += jode.getDimension();
263         }
264 
265         return dYdY0;
266 
267     }
268 
269     /** Extract the Jacobian matrix with respect to one parameter.
270      * @param state state from which to extract Jacobian matrix
271      * @param pName name of the parameter for the computed Jacobian matrix
272      * @return Jacobian matrix dY/dP with respect to the named parameter
273      */
274     public double[] extractParameterJacobian(final ODEState state, final String pName) {
275 
276         // get current state for this set of equations from the expandable fode
277         final double[] p = state.getSecondaryState(index);
278 
279         final double[] dYdP = new double[jode.getDimension()];
280         int i = jode.getDimension() * jode.getDimension();
281         for (final String knownParameter : jode.getParametersNames()) {
282             if (pName.equals(knownParameter)) {
283                 System.arraycopy(p, i, dYdP, 0, jode.getDimension());
284                 break;
285             }
286             i += jode.getDimension();
287         }
288 
289         return dYdP;
290 
291     }
292 
293     /** Check array dimensions.
294      * @param expected expected dimension
295      * @param array (may be null if expected is 0)
296      * @throws MathIllegalArgumentException if the array dimension does not match the expected one
297      */
298     private void checkDimension(final int expected, final Object array)
299         throws MathIllegalArgumentException {
300         int arrayDimension = (array == null) ? 0 : Array.getLength(array);
301         if (arrayDimension != expected) {
302             throw new MathIllegalArgumentException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
303                                                    arrayDimension, expected);
304         }
305     }
306 
307     /** Local implementation of secondary equations. */
308     private class JacobiansSecondaryODE implements SecondaryODE {
309 
310         /** {@inheritDoc} */
311         @Override
312         public int getDimension() {
313             return jode.getDimension() * (jode.getDimension() + jode.getParametersNames().size());
314         }
315 
316         /** {@inheritDoc} */
317         @Override
318         public double[] computeDerivatives(final double t, final double[] y, final double[] yDot,
319                                            final double[] z)
320             throws MathIllegalArgumentException, MathIllegalStateException {
321 
322             final double[] zDot = new double[z.length];
323 
324             // variational equations:
325             // from d[dy/dt]/dy0 and d[dy/dt]/dp to d[dy/dy0]/dt and d[dy/dp]/dt
326 
327             // compute Jacobian matrix with respect to primary state
328             double[][] dFdY = jode.computeMainStateJacobian(t, y, yDot);
329 
330             // Dispatch Jacobian matrix in the compound secondary state vector
331             for (int i = 0; i < jode.getDimension(); ++i) {
332                 final double[] dFdYi = dFdY[i];
333                 for (int j = 0; j < jode.getDimension(); ++j) {
334                     double s = 0;
335                     final int startIndex = j;
336                     int zIndex = startIndex;
337                     for (int l = 0; l < jode.getDimension(); ++l) {
338                         s += dFdYi[l] * z[zIndex];
339                         zIndex += jode.getDimension();
340                     }
341                     zDot[startIndex + i * jode.getDimension()] = s;
342                 }
343             }
344 
345             // compute Jacobian matrices with respect to parameters
346             int startIndex = jode.getDimension() * jode.getDimension();
347             for (final String name : jode.getParametersNames()) {
348                 final double[] dFdP = jode.computeParameterJacobian(t, y, yDot, name);
349                 for (int i = 0; i < jode.getDimension(); ++i) {
350                     final double[] dFdYi = dFdY[i];
351                     int zIndex = startIndex;
352                     double s = dFdP[i];
353                     for (int l = 0; l < jode.getDimension(); ++l) {
354                         s += dFdYi[l] * z[zIndex];
355                         zIndex++;
356                     }
357                     zDot[startIndex + i] = s;
358                 }
359                 startIndex += jode.getDimension();
360             }
361 
362             return zDot;
363 
364         }
365     }
366 
367     /**
368      * Special exception for equations mismatch.
369      */
370     public static class MismatchedEquations extends MathIllegalArgumentException {
371 
372         /** Serializable UID. */
373         private static final long serialVersionUID = 20120902L;
374 
375         /** Simple constructor. */
376         public MismatchedEquations() {
377             super(LocalizedODEFormats.UNMATCHED_ODE_IN_EXPANDED_SET);
378         }
379 
380     }
381 
382 }
383