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