001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.math3.ode;
018
019import java.lang.reflect.Array;
020import java.util.ArrayList;
021import java.util.Arrays;
022import java.util.List;
023
024import org.apache.commons.math3.exception.DimensionMismatchException;
025import org.apache.commons.math3.exception.MathIllegalArgumentException;
026import org.apache.commons.math3.exception.MaxCountExceededException;
027import org.apache.commons.math3.exception.util.LocalizedFormats;
028
029/**
030 * This class defines a set of {@link SecondaryEquations secondary equations} to
031 * compute the Jacobian matrices with respect to the initial state vector and, if
032 * any, to some parameters of the primary ODE set.
033 * <p>
034 * It is intended to be packed into an {@link ExpandableStatefulODE}
035 * in conjunction with a primary set of ODE, which may be:
036 * <ul>
037 * <li>a {@link FirstOrderDifferentialEquations}</li>
038 * <li>a {@link MainStateJacobianProvider}</li>
039 * </ul>
040 * In order to compute Jacobian matrices with respect to some parameters of the
041 * primary ODE set, the following parameter Jacobian providers may be set:
042 * <ul>
043 * <li>a {@link ParameterJacobianProvider}</li>
044 * <li>a {@link ParameterizedODE}</li>
045 * </ul>
046 * </p>
047 *
048 * @see ExpandableStatefulODE
049 * @see FirstOrderDifferentialEquations
050 * @see MainStateJacobianProvider
051 * @see ParameterJacobianProvider
052 * @see ParameterizedODE
053 *
054 * @since 3.0
055 */
056public class JacobianMatrices {
057
058    /** Expandable first order differential equation. */
059    private ExpandableStatefulODE efode;
060
061    /** Index of the instance in the expandable set. */
062    private int index;
063
064    /** FODE with exact primary Jacobian computation skill. */
065    private MainStateJacobianProvider jode;
066
067    /** FODE without exact parameter Jacobian computation skill. */
068    private ParameterizedODE pode;
069
070    /** Main state vector dimension. */
071    private int stateDim;
072
073    /** Selected parameters for parameter Jacobian computation. */
074    private ParameterConfiguration[] selectedParameters;
075
076    /** FODE with exact parameter Jacobian computation skill. */
077    private List<ParameterJacobianProvider> jacobianProviders;
078
079    /** Parameters dimension. */
080    private int paramDim;
081
082    /** Boolean for selected parameters consistency. */
083    private boolean dirtyParameter;
084
085    /** State and parameters Jacobian matrices in a row. */
086    private double[] matricesData;
087
088    /** Simple constructor for a secondary equations set computing Jacobian matrices.
089     * <p>
090     * Parameters must belong to the supported ones given by {@link
091     * Parameterizable#getParametersNames()}, so the primary set of differential
092     * equations must be {@link Parameterizable}.
093     * </p>
094     * <p>Note that each selection clears the previous selected parameters.</p>
095     *
096     * @param fode the primary first order differential equations set to extend
097     * @param hY step used for finite difference computation with respect to state vector
098     * @param parameters parameters to consider for Jacobian matrices processing
099     * (may be null if parameters Jacobians is not desired)
100     * @exception DimensionMismatchException if there is a dimension mismatch between
101     * the steps array {@code hY} and the equation dimension
102     */
103    public JacobianMatrices(final FirstOrderDifferentialEquations fode, final double[] hY,
104                            final String... parameters)
105        throws DimensionMismatchException {
106        this(new MainStateJacobianWrapper(fode, hY), parameters);
107    }
108
109    /** Simple constructor for a secondary equations set computing Jacobian matrices.
110     * <p>
111     * Parameters must belong to the supported ones given by {@link
112     * Parameterizable#getParametersNames()}, so the primary set of differential
113     * equations must be {@link Parameterizable}.
114     * </p>
115     * <p>Note that each selection clears the previous selected parameters.</p>
116     *
117     * @param jode the primary first order differential equations set to extend
118     * @param parameters parameters to consider for Jacobian matrices processing
119     * (may be null if parameters Jacobians is not desired)
120     */
121    public JacobianMatrices(final MainStateJacobianProvider jode,
122                            final String... parameters) {
123
124        this.efode = null;
125        this.index = -1;
126
127        this.jode = jode;
128        this.pode = null;
129
130        this.stateDim = jode.getDimension();
131
132        if (parameters == null) {
133            selectedParameters = null;
134            paramDim = 0;
135        } else {
136            this.selectedParameters = new ParameterConfiguration[parameters.length];
137            for (int i = 0; i < parameters.length; ++i) {
138                selectedParameters[i] = new ParameterConfiguration(parameters[i], Double.NaN);
139            }
140            paramDim = parameters.length;
141        }
142        this.dirtyParameter = false;
143
144        this.jacobianProviders = new ArrayList<ParameterJacobianProvider>();
145
146        // set the default initial state Jacobian to the identity
147        // and the default initial parameters Jacobian to the null matrix
148        matricesData = new double[(stateDim + paramDim) * stateDim];
149        for (int i = 0; i < stateDim; ++i) {
150            matricesData[i * (stateDim + 1)] = 1.0;
151        }
152
153    }
154
155    /** Register the variational equations for the Jacobians matrices to the expandable set.
156     * @param expandable expandable set into which variational equations should be registered
157     * @throws DimensionMismatchException if the dimension of the partial state does not
158     * match the selected equations set dimension
159     * @exception MismatchedEquations if the primary set of the expandable set does
160     * not match the one used to build the instance
161     * @see ExpandableStatefulODE#addSecondaryEquations(SecondaryEquations)
162     */
163    public void registerVariationalEquations(final ExpandableStatefulODE expandable)
164        throws DimensionMismatchException, MismatchedEquations {
165
166        // safety checks
167        final FirstOrderDifferentialEquations ode = (jode instanceof MainStateJacobianWrapper) ?
168                                                    ((MainStateJacobianWrapper) jode).ode :
169                                                    jode;
170        if (expandable.getPrimary() != ode) {
171            throw new MismatchedEquations();
172        }
173
174        efode = expandable;
175        index = efode.addSecondaryEquations(new JacobiansSecondaryEquations());
176        efode.setSecondaryState(index, matricesData);
177
178    }
179
180    /** Add a parameter Jacobian provider.
181     * @param provider the parameter Jacobian provider to compute exactly the parameter Jacobian matrix
182     */
183    public void addParameterJacobianProvider(final ParameterJacobianProvider provider) {
184        jacobianProviders.add(provider);
185    }
186
187    /** Set a parameter Jacobian provider.
188     * @param parameterizedOde the parameterized ODE to compute the parameter Jacobian matrix using finite differences
189     */
190    public void setParameterizedODE(final ParameterizedODE parameterizedOde) {
191        this.pode = parameterizedOde;
192        dirtyParameter = true;
193    }
194
195    /** Set the step associated to a parameter in order to compute by finite
196     *  difference the Jacobian matrix.
197     * <p>
198     * Needed if and only if the primary ODE set is a {@link ParameterizedODE}.
199     * </p>
200     * <p>
201     * Given a non zero parameter value pval for the parameter, a reasonable value
202     * for such a step is {@code pval * FastMath.sqrt(Precision.EPSILON)}.
203     * </p>
204     * <p>
205     * A zero value for such a step doesn't enable to compute the parameter Jacobian matrix.
206     * </p>
207     * @param parameter parameter to consider for Jacobian processing
208     * @param hP step for Jacobian finite difference computation w.r.t. the specified parameter
209     * @see ParameterizedODE
210     * @exception UnknownParameterException if the parameter is not supported
211     */
212    public void setParameterStep(final String parameter, final double hP)
213        throws UnknownParameterException {
214
215        for (ParameterConfiguration param: selectedParameters) {
216            if (parameter.equals(param.getParameterName())) {
217                param.setHP(hP);
218                dirtyParameter = true;
219                return;
220            }
221        }
222
223        throw new UnknownParameterException(parameter);
224
225    }
226
227    /** Set the initial value of the Jacobian matrix with respect to state.
228     * <p>
229     * If this method is not called, the initial value of the Jacobian
230     * matrix with respect to state is set to identity.
231     * </p>
232     * @param dYdY0 initial Jacobian matrix w.r.t. state
233     * @exception DimensionMismatchException if matrix dimensions are incorrect
234     */
235    public void setInitialMainStateJacobian(final double[][] dYdY0)
236        throws DimensionMismatchException {
237
238        // Check dimensions
239        checkDimension(stateDim, dYdY0);
240        checkDimension(stateDim, dYdY0[0]);
241
242        // store the matrix in row major order as a single dimension array
243        int i = 0;
244        for (final double[] row : dYdY0) {
245            System.arraycopy(row, 0, matricesData, i, stateDim);
246            i += stateDim;
247        }
248
249        if (efode != null) {
250            efode.setSecondaryState(index, matricesData);
251        }
252
253    }
254
255    /** Set the initial value of a column of the Jacobian matrix with respect to one parameter.
256     * <p>
257     * If this method is not called for some parameter, the initial value of
258     * the column of the Jacobian matrix with respect to this parameter is set to zero.
259     * </p>
260     * @param pName parameter name
261     * @param dYdP initial Jacobian column vector with respect to the parameter
262     * @exception UnknownParameterException if a parameter is not supported
263     * @throws DimensionMismatchException if the column vector does not match state dimension
264     */
265    public void setInitialParameterJacobian(final String pName, final double[] dYdP)
266        throws UnknownParameterException, DimensionMismatchException {
267
268        // Check dimensions
269        checkDimension(stateDim, dYdP);
270
271        // store the column in a global single dimension array
272        int i = stateDim * stateDim;
273        for (ParameterConfiguration param: selectedParameters) {
274            if (pName.equals(param.getParameterName())) {
275                System.arraycopy(dYdP, 0, matricesData, i, stateDim);
276                if (efode != null) {
277                    efode.setSecondaryState(index, matricesData);
278                }
279                return;
280            }
281            i += stateDim;
282        }
283
284        throw new UnknownParameterException(pName);
285
286    }
287
288    /** Get the current value of the Jacobian matrix with respect to state.
289     * @param dYdY0 current Jacobian matrix with respect to state.
290     */
291    public void getCurrentMainSetJacobian(final double[][] dYdY0) {
292
293        // get current state for this set of equations from the expandable fode
294        double[] p = efode.getSecondaryState(index);
295
296        int j = 0;
297        for (int i = 0; i < stateDim; i++) {
298            System.arraycopy(p, j, dYdY0[i], 0, stateDim);
299            j += stateDim;
300        }
301
302    }
303
304    /** Get the current value of the Jacobian matrix with respect to one parameter.
305     * @param pName name of the parameter for the computed Jacobian matrix
306     * @param dYdP current Jacobian matrix with respect to the named parameter
307     */
308    public void getCurrentParameterJacobian(String pName, final double[] dYdP) {
309
310        // get current state for this set of equations from the expandable fode
311        double[] p = efode.getSecondaryState(index);
312
313        int i = stateDim * stateDim;
314        for (ParameterConfiguration param: selectedParameters) {
315            if (param.getParameterName().equals(pName)) {
316                System.arraycopy(p, i, dYdP, 0, stateDim);
317                return;
318            }
319            i += stateDim;
320        }
321
322    }
323
324    /** Check array dimensions.
325     * @param expected expected dimension
326     * @param array (may be null if expected is 0)
327     * @throws DimensionMismatchException if the array dimension does not match the expected one
328     */
329    private void checkDimension(final int expected, final Object array)
330        throws DimensionMismatchException {
331        int arrayDimension = (array == null) ? 0 : Array.getLength(array);
332        if (arrayDimension != expected) {
333            throw new DimensionMismatchException(arrayDimension, expected);
334        }
335    }
336
337    /** Local implementation of secondary equations.
338     * <p>
339     * This class is an inner class to ensure proper scheduling of calls
340     * by forcing the use of {@link JacobianMatrices#registerVariationalEquations(ExpandableStatefulODE)}.
341     * </p>
342     */
343    private class JacobiansSecondaryEquations implements SecondaryEquations {
344
345        /** {@inheritDoc} */
346        public int getDimension() {
347            return stateDim * (stateDim + paramDim);
348        }
349
350        /** {@inheritDoc} */
351        public void computeDerivatives(final double t, final double[] y, final double[] yDot,
352                                       final double[] z, final double[] zDot)
353            throws MaxCountExceededException, DimensionMismatchException {
354
355            // Lazy initialization
356            if (dirtyParameter && (paramDim != 0)) {
357                jacobianProviders.add(new ParameterJacobianWrapper(jode, pode, selectedParameters));
358                dirtyParameter = false;
359            }
360
361            // variational equations:
362            // from d[dy/dt]/dy0 and d[dy/dt]/dp to d[dy/dy0]/dt and d[dy/dp]/dt
363
364            // compute Jacobian matrix with respect to primary state
365            double[][] dFdY = new double[stateDim][stateDim];
366            jode.computeMainStateJacobian(t, y, yDot, dFdY);
367
368            // Dispatch Jacobian matrix in the compound secondary state vector
369            for (int i = 0; i < stateDim; ++i) {
370                final double[] dFdYi = dFdY[i];
371                for (int j = 0; j < stateDim; ++j) {
372                    double s = 0;
373                    final int startIndex = j;
374                    int zIndex = startIndex;
375                    for (int l = 0; l < stateDim; ++l) {
376                        s += dFdYi[l] * z[zIndex];
377                        zIndex += stateDim;
378                    }
379                    zDot[startIndex + i * stateDim] = s;
380                }
381            }
382
383            if (paramDim != 0) {
384                // compute Jacobian matrices with respect to parameters
385                double[] dFdP = new double[stateDim];
386                int startIndex = stateDim * stateDim;
387                for (ParameterConfiguration param: selectedParameters) {
388                    boolean found = false;
389                    for (int k = 0 ; (!found) && (k < jacobianProviders.size()); ++k) {
390                        final ParameterJacobianProvider provider = jacobianProviders.get(k);
391                        if (provider.isSupported(param.getParameterName())) {
392                            provider.computeParameterJacobian(t, y, yDot,
393                                                              param.getParameterName(), dFdP);
394                            for (int i = 0; i < stateDim; ++i) {
395                                final double[] dFdYi = dFdY[i];
396                                int zIndex = startIndex;
397                                double s = dFdP[i];
398                                for (int l = 0; l < stateDim; ++l) {
399                                    s += dFdYi[l] * z[zIndex];
400                                    zIndex++;
401                                }
402                                zDot[startIndex + i] = s;
403                            }
404                            found = true;
405                        }
406                    }
407                    if (! found) {
408                        Arrays.fill(zDot, startIndex, startIndex + stateDim, 0.0);
409                    }
410                    startIndex += stateDim;
411                }
412            }
413
414        }
415    }
416
417    /** Wrapper class to compute jacobian matrices by finite differences for ODE
418     *  which do not compute them by themselves.
419     */
420    private static class MainStateJacobianWrapper implements MainStateJacobianProvider {
421
422        /** Raw ODE without jacobians computation skill to be wrapped into a MainStateJacobianProvider. */
423        private final FirstOrderDifferentialEquations ode;
424
425        /** Steps for finite difference computation of the jacobian df/dy w.r.t. state. */
426        private final double[] hY;
427
428        /** Wrap a {@link FirstOrderDifferentialEquations} into a {@link MainStateJacobianProvider}.
429         * @param ode original ODE problem, without jacobians computation skill
430         * @param hY step sizes to compute the jacobian df/dy
431         * @exception DimensionMismatchException if there is a dimension mismatch between
432         * the steps array {@code hY} and the equation dimension
433         */
434        MainStateJacobianWrapper(final FirstOrderDifferentialEquations ode,
435                                 final double[] hY)
436            throws DimensionMismatchException {
437            this.ode = ode;
438            this.hY = hY.clone();
439            if (hY.length != ode.getDimension()) {
440                throw new DimensionMismatchException(ode.getDimension(), hY.length);
441            }
442        }
443
444        /** {@inheritDoc} */
445        public int getDimension() {
446            return ode.getDimension();
447        }
448
449        /** {@inheritDoc} */
450        public void computeDerivatives(double t, double[] y, double[] yDot)
451            throws MaxCountExceededException, DimensionMismatchException {
452            ode.computeDerivatives(t, y, yDot);
453        }
454
455        /** {@inheritDoc} */
456        public void computeMainStateJacobian(double t, double[] y, double[] yDot, double[][] dFdY)
457            throws MaxCountExceededException, DimensionMismatchException {
458
459            final int n = ode.getDimension();
460            final double[] tmpDot = new double[n];
461
462            for (int j = 0; j < n; ++j) {
463                final double savedYj = y[j];
464                y[j] += hY[j];
465                ode.computeDerivatives(t, y, tmpDot);
466                for (int i = 0; i < n; ++i) {
467                    dFdY[i][j] = (tmpDot[i] - yDot[i]) / hY[j];
468                }
469                y[j] = savedYj;
470            }
471        }
472
473    }
474
475    /**
476     * Special exception for equations mismatch.
477     * @since 3.1
478     */
479    public static class MismatchedEquations extends MathIllegalArgumentException {
480
481        /** Serializable UID. */
482        private static final long serialVersionUID = 20120902L;
483
484        /** Simple constructor. */
485        public MismatchedEquations() {
486            super(LocalizedFormats.UNMATCHED_ODE_IN_EXPANDED_SET);
487        }
488
489    }
490
491}
492