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