View Javadoc
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  
18  package org.apache.commons.math4.legacy.ode;
19  
20  import java.io.Serializable;
21  import java.util.ArrayList;
22  import java.util.List;
23  
24  import org.apache.commons.math4.legacy.exception.DimensionMismatchException;
25  import org.apache.commons.math4.legacy.exception.MathIllegalArgumentException;
26  import org.apache.commons.math4.legacy.exception.MaxCountExceededException;
27  import org.apache.commons.math4.legacy.exception.util.LocalizedFormats;
28  import org.apache.commons.math4.legacy.ode.sampling.StepHandler;
29  import org.apache.commons.math4.legacy.ode.sampling.StepInterpolator;
30  import org.apache.commons.math4.core.jdkmath.JdkMath;
31  
32  /**
33   * This class stores all information provided by an ODE integrator
34   * during the integration process and build a continuous model of the
35   * solution from this.
36   *
37   * <p>This class act as a step handler from the integrator point of
38   * view. It is called iteratively during the integration process and
39   * stores a copy of all steps information in a sorted collection for
40   * later use. Once the integration process is over, the user can use
41   * the {@link #setInterpolatedTime setInterpolatedTime} and {@link
42   * #getInterpolatedState getInterpolatedState} to retrieve this
43   * information at any time. It is important to wait for the
44   * integration to be over before attempting to call {@link
45   * #setInterpolatedTime setInterpolatedTime} because some internal
46   * variables are set only once the last step has been handled.</p>
47   *
48   * <p>This is useful for example if the main loop of the user
49   * application should remain independent from the integration process
50   * or if one needs to mimic the behaviour of an analytical model
51   * despite a numerical model is used (i.e. one needs the ability to
52   * get the model value at any time or to navigate through the
53   * data).</p>
54   *
55   * <p>If problem modeling is done with several separate
56   * integration phases for contiguous intervals, the same
57   * ContinuousOutputModel can be used as step handler for all
58   * integration phases as long as they are performed in order and in
59   * the same direction. As an example, one can extrapolate the
60   * trajectory of a satellite with one model (i.e. one set of
61   * differential equations) up to the beginning of a maneuver, use
62   * another more complex model including thrusters modeling and
63   * accurate attitude control during the maneuver, and revert to the
64   * first model after the end of the maneuver. If the same continuous
65   * output model handles the steps of all integration phases, the user
66   * do not need to bother when the maneuver begins or ends, he has all
67   * the data available in a transparent manner.</p>
68   *
69   * <p>An important feature of this class is that it implements the
70   * <code>Serializable</code> interface. This means that the result of
71   * an integration can be serialized and reused later (if stored into a
72   * persistent medium like a file system or a database) or elsewhere (if
73   * sent to another application). Only the result of the integration is
74   * stored, there is no reference to the integrated problem by
75   * itself.</p>
76   *
77   * <p>One should be aware that the amount of data stored in a
78   * ContinuousOutputModel instance can be important if the state vector
79   * is large, if the integration interval is long or if the steps are
80   * small (which can result from small tolerance settings in {@link
81   * org.apache.commons.math4.legacy.ode.nonstiff.AdaptiveStepsizeIntegrator adaptive
82   * step size integrators}).</p>
83   *
84   * @see StepHandler
85   * @see StepInterpolator
86   * @since 1.2
87   */
88  
89  public class ContinuousOutputModel
90    implements StepHandler, Serializable {
91  
92      /** Serializable version identifier. */
93      private static final long serialVersionUID = -1417964919405031606L;
94  
95      /** Initial integration time. */
96      private double initialTime;
97  
98      /** Final integration time. */
99      private double finalTime;
100 
101     /** Integration direction indicator. */
102     private boolean forward;
103 
104     /** Current interpolator index. */
105     private int index;
106 
107     /** Steps table. */
108     private List<StepInterpolator> steps;
109 
110   /** Simple constructor.
111    * Build an empty continuous output model.
112    */
113   public ContinuousOutputModel() {
114     steps = new ArrayList<>();
115     initialTime = Double.NaN;
116     finalTime   = Double.NaN;
117     forward     = true;
118     index       = 0;
119   }
120 
121   /** Append another model at the end of the instance.
122    * @param model model to add at the end of the instance
123    * @exception MathIllegalArgumentException if the model to append is not
124    * compatible with the instance (dimension of the state vector,
125    * propagation direction, hole between the dates)
126    * @exception MaxCountExceededException if the number of functions evaluations is exceeded
127    * during step finalization
128    */
129   public void append(final ContinuousOutputModel model)
130     throws MathIllegalArgumentException, MaxCountExceededException {
131 
132     if (model.steps.isEmpty()) {
133       return;
134     }
135 
136     if (steps.isEmpty()) {
137       initialTime = model.initialTime;
138       forward     = model.forward;
139     } else {
140 
141       if (getInterpolatedState().length != model.getInterpolatedState().length) {
142           throw new DimensionMismatchException(model.getInterpolatedState().length,
143                                                getInterpolatedState().length);
144       }
145 
146       if (forward ^ model.forward) {
147           throw new MathIllegalArgumentException(LocalizedFormats.PROPAGATION_DIRECTION_MISMATCH);
148       }
149 
150       final StepInterpolator lastInterpolator = steps.get(index);
151       final double current  = lastInterpolator.getCurrentTime();
152       final double previous = lastInterpolator.getPreviousTime();
153       final double step = current - previous;
154       final double gap = model.getInitialTime() - current;
155       if (JdkMath.abs(gap) > 1.0e-3 * JdkMath.abs(step)) {
156         throw new MathIllegalArgumentException(LocalizedFormats.HOLE_BETWEEN_MODELS_TIME_RANGES,
157                                                JdkMath.abs(gap));
158       }
159     }
160 
161     for (StepInterpolator interpolator : model.steps) {
162       steps.add(interpolator.copy());
163     }
164 
165     index = steps.size() - 1;
166     finalTime = (steps.get(index)).getCurrentTime();
167   }
168 
169   /** {@inheritDoc} */
170   @Override
171   public void init(double t0, double[] y0, double t) {
172     initialTime = Double.NaN;
173     finalTime   = Double.NaN;
174     forward     = true;
175     index       = 0;
176     steps.clear();
177   }
178 
179   /** Handle the last accepted step.
180    * A copy of the information provided by the last step is stored in
181    * the instance for later use.
182    * @param interpolator interpolator for the last accepted step.
183    * @param isLast true if the step is the last one
184    * @exception MaxCountExceededException if the number of functions evaluations is exceeded
185    * during step finalization
186    */
187   @Override
188 public void handleStep(final StepInterpolator interpolator, final boolean isLast)
189       throws MaxCountExceededException {
190 
191     if (steps.isEmpty()) {
192       initialTime = interpolator.getPreviousTime();
193       forward     = interpolator.isForward();
194     }
195 
196     steps.add(interpolator.copy());
197 
198     if (isLast) {
199       finalTime = interpolator.getCurrentTime();
200       index     = steps.size() - 1;
201     }
202   }
203 
204   /**
205    * Get the initial integration time.
206    * @return initial integration time
207    */
208   public double getInitialTime() {
209     return initialTime;
210   }
211 
212   /**
213    * Get the final integration time.
214    * @return final integration time
215    */
216   public double getFinalTime() {
217     return finalTime;
218   }
219 
220   /**
221    * Get the time of the interpolated point.
222    * If {@link #setInterpolatedTime} has not been called, it returns
223    * the final integration time.
224    * @return interpolation point time
225    */
226   public double getInterpolatedTime() {
227     return steps.get(index).getInterpolatedTime();
228   }
229 
230   /** Set the time of the interpolated point.
231    * <p>This method should <strong>not</strong> be called before the
232    * integration is over because some internal variables are set only
233    * once the last step has been handled.</p>
234    * <p>Setting the time outside of the integration interval is now
235    * allowed, but should be used with care since the accuracy of the
236    * interpolator will probably be very poor far from this interval.
237    * This allowance has been added to simplify implementation of search
238    * algorithms near the interval endpoints.</p>
239    * <p>Note that each time this method is called, the internal arrays
240    * returned in {@link #getInterpolatedState()}, {@link
241    * #getInterpolatedDerivatives()} and {@link #getInterpolatedSecondaryState(int)}
242    * <em>will</em> be overwritten. So if their content must be preserved
243    * across several calls, user must copy them.</p>
244    * @param time time of the interpolated point
245    * @see #getInterpolatedState()
246    * @see #getInterpolatedDerivatives()
247    * @see #getInterpolatedSecondaryState(int)
248    */
249   public void setInterpolatedTime(final double time) {
250 
251       // initialize the search with the complete steps table
252       int iMin = 0;
253       final StepInterpolator sMin = steps.get(iMin);
254       double tMin = 0.5 * (sMin.getPreviousTime() + sMin.getCurrentTime());
255 
256       int iMax = steps.size() - 1;
257       final StepInterpolator sMax = steps.get(iMax);
258       double tMax = 0.5 * (sMax.getPreviousTime() + sMax.getCurrentTime());
259 
260       // handle points outside of the integration interval
261       // or in the first and last step
262       if (locatePoint(time, sMin) <= 0) {
263         index = iMin;
264         sMin.setInterpolatedTime(time);
265         return;
266       }
267       if (locatePoint(time, sMax) >= 0) {
268         index = iMax;
269         sMax.setInterpolatedTime(time);
270         return;
271       }
272 
273       // reduction of the table slice size
274       while (iMax - iMin > 5) {
275 
276         // use the last estimated index as the splitting index
277         final StepInterpolator si = steps.get(index);
278         final int location = locatePoint(time, si);
279         if (location < 0) {
280           iMax = index;
281           tMax = 0.5 * (si.getPreviousTime() + si.getCurrentTime());
282         } else if (location > 0) {
283           iMin = index;
284           tMin = 0.5 * (si.getPreviousTime() + si.getCurrentTime());
285         } else {
286           // we have found the target step, no need to continue searching
287           si.setInterpolatedTime(time);
288           return;
289         }
290 
291         // compute a new estimate of the index in the reduced table slice
292         final int iMed = (iMin + iMax) / 2;
293         final StepInterpolator sMed = steps.get(iMed);
294         final double tMed = 0.5 * (sMed.getPreviousTime() + sMed.getCurrentTime());
295 
296         if (JdkMath.abs(tMed - tMin) < 1e-6 || JdkMath.abs(tMax - tMed) < 1e-6) {
297           // too close to the bounds, we estimate using a simple dichotomy
298           index = iMed;
299         } else {
300           // estimate the index using a reverse quadratic polynom
301           // (reverse means we have i = P(t), thus allowing to simply
302           // compute index = P(time) rather than solving a quadratic equation)
303           final double d12 = tMax - tMed;
304           final double d23 = tMed - tMin;
305           final double d13 = tMax - tMin;
306           final double dt1 = time - tMax;
307           final double dt2 = time - tMed;
308           final double dt3 = time - tMin;
309           final double iLagrange = ((dt2 * dt3 * d23) * iMax -
310                                     (dt1 * dt3 * d13) * iMed +
311                                     (dt1 * dt2 * d12) * iMin) /
312                                    (d12 * d23 * d13);
313           index = (int) JdkMath.rint(iLagrange);
314         }
315 
316         // force the next size reduction to be at least one tenth
317         final int low  = JdkMath.max(iMin + 1, (9 * iMin + iMax) / 10);
318         final int high = JdkMath.min(iMax - 1, (iMin + 9 * iMax) / 10);
319         if (index < low) {
320           index = low;
321         } else if (index > high) {
322           index = high;
323         }
324       }
325 
326       // now the table slice is very small, we perform an iterative search
327       index = iMin;
328       while (index <= iMax && locatePoint(time, steps.get(index)) > 0) {
329         ++index;
330       }
331 
332       steps.get(index).setInterpolatedTime(time);
333   }
334 
335   /**
336    * Get the state vector of the interpolated point.
337    * <p>The returned vector is a reference to a reused array, so
338    * it should not be modified and it should be copied if it needs
339    * to be preserved across several calls to the associated
340    * {@link #setInterpolatedTime(double)} method.</p>
341    * @return state vector at time {@link #getInterpolatedTime}
342    * @exception MaxCountExceededException if the number of functions evaluations is exceeded
343    * @see #setInterpolatedTime(double)
344    * @see #getInterpolatedDerivatives()
345    * @see #getInterpolatedSecondaryState(int)
346    * @see #getInterpolatedSecondaryDerivatives(int)
347    */
348   public double[] getInterpolatedState() throws MaxCountExceededException {
349     return steps.get(index).getInterpolatedState();
350   }
351 
352   /**
353    * Get the derivatives of the state vector of the interpolated point.
354    * <p>The returned vector is a reference to a reused array, so
355    * it should not be modified and it should be copied if it needs
356    * to be preserved across several calls to the associated
357    * {@link #setInterpolatedTime(double)} method.</p>
358    * @return derivatives of the state vector at time {@link #getInterpolatedTime}
359    * @exception MaxCountExceededException if the number of functions evaluations is exceeded
360    * @see #setInterpolatedTime(double)
361    * @see #getInterpolatedState()
362    * @see #getInterpolatedSecondaryState(int)
363    * @see #getInterpolatedSecondaryDerivatives(int)
364    * @since 3.4
365    */
366   public double[] getInterpolatedDerivatives() throws MaxCountExceededException {
367     return steps.get(index).getInterpolatedDerivatives();
368   }
369 
370   /** Get the interpolated secondary state corresponding to the secondary equations.
371    * <p>The returned vector is a reference to a reused array, so
372    * it should not be modified and it should be copied if it needs
373    * to be preserved across several calls to the associated
374    * {@link #setInterpolatedTime(double)} method.</p>
375    * @param secondaryStateIndex index of the secondary set, as returned by {@link
376    * org.apache.commons.math4.legacy.ode.ExpandableStatefulODE#addSecondaryEquations(
377    * org.apache.commons.math4.legacy.ode.SecondaryEquations)
378    * ExpandableStatefulODE.addSecondaryEquations(SecondaryEquations)}
379    * @return interpolated secondary state at the current interpolation date
380    * @see #setInterpolatedTime(double)
381    * @see #getInterpolatedState()
382    * @see #getInterpolatedDerivatives()
383    * @see #getInterpolatedSecondaryDerivatives(int)
384    * @since 3.2
385    * @exception MaxCountExceededException if the number of functions evaluations is exceeded
386    */
387   public double[] getInterpolatedSecondaryState(final int secondaryStateIndex)
388     throws MaxCountExceededException {
389     return steps.get(index).getInterpolatedSecondaryState(secondaryStateIndex);
390   }
391 
392   /** Get the interpolated secondary derivatives corresponding to the secondary equations.
393    * <p>The returned vector is a reference to a reused array, so
394    * it should not be modified and it should be copied if it needs
395    * to be preserved across several calls to the associated
396    * {@link #setInterpolatedTime(double)} method.</p>
397    * @param secondaryStateIndex index of the secondary set, as returned by {@link
398    * org.apache.commons.math4.legacy.ode.ExpandableStatefulODE#addSecondaryEquations(
399    * org.apache.commons.math4.legacy.ode.SecondaryEquations)
400    * ExpandableStatefulODE.addSecondaryEquations(SecondaryEquations)}
401    * @return interpolated secondary derivatives at the current interpolation date
402    * @see #setInterpolatedTime(double)
403    * @see #getInterpolatedState()
404    * @see #getInterpolatedDerivatives()
405    * @see #getInterpolatedSecondaryState(int)
406    * @since 3.4
407    * @exception MaxCountExceededException if the number of functions evaluations is exceeded
408    */
409   public double[] getInterpolatedSecondaryDerivatives(final int secondaryStateIndex)
410     throws MaxCountExceededException {
411     return steps.get(index).getInterpolatedSecondaryDerivatives(secondaryStateIndex);
412   }
413 
414   /** Compare a step interval and a double.
415    * @param time point to locate
416    * @param interval step interval
417    * @return -1 if the double is before the interval, 0 if it is in
418    * the interval, and +1 if it is after the interval, according to
419    * the interval direction
420    */
421   private int locatePoint(final double time, final StepInterpolator interval) {
422     if (forward) {
423       if (time < interval.getPreviousTime()) {
424         return -1;
425       } else if (time > interval.getCurrentTime()) {
426         return +1;
427       } else {
428         return 0;
429       }
430     }
431     if (time > interval.getPreviousTime()) {
432       return -1;
433     } else if (time < interval.getCurrentTime()) {
434       return +1;
435     } else {
436       return 0;
437     }
438   }
439 }