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    *      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  
18  /*
19   * This is not the original file distributed by the Apache Software Foundation
20   * It has been modified by the Hipparchus project
21   */
22  
23  package org.hipparchus.ode;
24  
25  import java.io.Serializable;
26  import java.util.ArrayList;
27  import java.util.List;
28  
29  import org.hipparchus.exception.LocalizedCoreFormats;
30  import org.hipparchus.exception.MathIllegalArgumentException;
31  import org.hipparchus.exception.MathIllegalStateException;
32  import org.hipparchus.ode.sampling.ODEStateInterpolator;
33  import org.hipparchus.ode.sampling.ODEStepHandler;
34  import org.hipparchus.util.FastMath;
35  
36  /**
37   * This class stores all information provided by an ODE integrator
38   * during the integration process and build a continuous model of the
39   * solution from this.
40   *
41   * <p>This class act as a step handler from the integrator point of
42   * view. It is called iteratively during the integration process and
43   * stores a copy of all steps information in a sorted collection for
44   * later use. Once the integration process is over, the user can use
45   * the {@link #getInterpolatedState(double) getInterpolatedState}
46   * method to retrieve this information at any time. It is important
47   * to wait for the integration to be over before attempting to call
48   * {@link #getInterpolatedState(double) getInterpolatedState} because
49   * some internal variables are set only once the last step has been
50   * handled.</p>
51   *
52   * <p>This is useful for example if the main loop of the user
53   * application should remain independent from the integration process
54   * or if one needs to mimic the behaviour of an analytical model
55   * despite a numerical model is used (i.e. one needs the ability to
56   * get the model value at any time or to navigate through the
57   * data).</p>
58   *
59   * <p>If problem modeling is done with several separate
60   * integration phases for contiguous intervals, the same
61   * DenseOutputModel can be used as step handler for all
62   * integration phases as long as they are performed in order and in
63   * the same direction. As an example, one can extrapolate the
64   * trajectory of a satellite with one model (i.e. one set of
65   * differential equations) up to the beginning of a maneuver, use
66   * another more complex model including thrusters modeling and
67   * accurate attitude control during the maneuver, and revert to the
68   * first model after the end of the maneuver. If the same continuous
69   * output model handles the steps of all integration phases, the user
70   * do not need to bother when the maneuver begins or ends, he has all
71   * the data available in a transparent manner.</p>
72   *
73   * <p>An important feature of this class is that it implements the
74   * <code>Serializable</code> interface. This means that the result of
75   * an integration can be serialized and reused later (if stored into a
76   * persistent medium like a filesystem or a database) or elsewhere (if
77   * sent to another application). Only the result of the integration is
78   * stored, there is no reference to the integrated problem by
79   * itself.</p>
80   *
81   * <p>One should be aware that the amount of data stored in a
82   * DenseOutputModel instance can be important if the state vector
83   * is large, if the integration interval is long or if the steps are
84   * small (which can result from small tolerance settings in {@link
85   * org.hipparchus.ode.nonstiff.AdaptiveStepsizeIntegrator adaptive
86   * step size integrators}).</p>
87   *
88   * @see ODEStepHandler
89   * @see ODEStateInterpolator
90   */
91  
92  public class DenseOutputModel implements ODEStepHandler, Serializable {
93  
94      /** Serializable version identifier */
95      private static final long serialVersionUID = 20160328L;
96  
97      /** Initial integration time. */
98      private double initialTime;
99  
100     /** Final integration time. */
101     private double finalTime;
102 
103     /** Integration direction indicator. */
104     private boolean forward;
105 
106     /** Current interpolator index. */
107     private int index;
108 
109     /** Steps table. */
110     private List<ODEStateInterpolator> steps;
111 
112     /** Simple constructor.
113      * Build an empty continuous output model.
114      */
115     public DenseOutputModel() {
116         steps       = new ArrayList<>();
117         initialTime = Double.NaN;
118         finalTime   = Double.NaN;
119         forward     = true;
120         index       = 0;
121     }
122 
123     /** Append another model at the end of the instance.
124      * @param model model to add at the end of the instance
125      * @exception MathIllegalArgumentException if the model to append is not
126      * compatible with the instance (dimension of the state vector,
127      * propagation direction, hole between the dates)
128      * @exception MathIllegalStateException if the number of functions evaluations is exceeded
129      * during step finalization
130      */
131     public void append(final DenseOutputModel model)
132         throws MathIllegalArgumentException, MathIllegalStateException {
133 
134         if (model.steps.isEmpty()) {
135             return;
136         }
137 
138         if (steps.isEmpty()) {
139             initialTime = model.initialTime;
140             forward     = model.forward;
141         } else {
142 
143             final ODEStateAndDerivative s1 = steps.get(0).getPreviousState();
144             final ODEStateAndDerivative s2 = model.steps.get(0).getPreviousState();
145             checkDimensionsEquality(s1.getPrimaryStateDimension(), s2.getPrimaryStateDimension());
146             checkDimensionsEquality(s1.getNumberOfSecondaryStates(), s2.getNumberOfSecondaryStates());
147             for (int i = 0; i < s1.getNumberOfSecondaryStates(); ++i) {
148                 checkDimensionsEquality(s1.getSecondaryStateDimension(i), s2.getSecondaryStateDimension(i));
149             }
150 
151             if (forward ^ model.forward) {
152                 throw new MathIllegalArgumentException(LocalizedODEFormats.PROPAGATION_DIRECTION_MISMATCH);
153             }
154 
155             final ODEStateInterpolator lastInterpolator = steps.get(index);
156             final double current  = lastInterpolator.getCurrentState().getTime();
157             final double previous = lastInterpolator.getPreviousState().getTime();
158             final double step = current - previous;
159             final double gap = model.getInitialTime() - current;
160             if (FastMath.abs(gap) > 1.0e-3 * FastMath.abs(step)) {
161                 throw new MathIllegalArgumentException(LocalizedODEFormats.HOLE_BETWEEN_MODELS_TIME_RANGES,
162                                                        FastMath.abs(gap));
163             }
164 
165         }
166 
167         steps.addAll(model.steps);
168 
169         index = steps.size() - 1;
170         finalTime = (steps.get(index)).getCurrentState().getTime();
171 
172     }
173 
174     /** Check dimensions equality.
175      * @param d1 first dimension
176      * @param d2 second dimansion
177      * @exception MathIllegalArgumentException if dimensions do not match
178      */
179     private void checkDimensionsEquality(final int d1, final int d2)
180         throws MathIllegalArgumentException {
181         if (d1 != d2) {
182             throw new MathIllegalArgumentException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
183                                                    d2, d1);
184         }
185     }
186 
187     /** {@inheritDoc} */
188     @Override
189     public void init(final ODEStateAndDerivative initialState, final double targetTime) {
190         initialTime    = initialState.getTime();
191         this.finalTime = targetTime;
192         forward        = true;
193         index          = 0;
194         steps.clear();
195     }
196 
197     /** {@inheritDoc} */
198     @Override
199     public void handleStep(final ODEStateInterpolator interpolator) {
200         if (steps.isEmpty()) {
201             initialTime = interpolator.getPreviousState().getTime();
202             forward     = interpolator.isForward();
203         }
204         steps.add(interpolator);
205     }
206 
207     /** {@inheritDoc} */
208     @Override
209     public void finish(final ODEStateAndDerivative finalState) {
210         finalTime = finalState.getTime();
211         index     = steps.size() - 1;
212     }
213 
214     /**
215      * Get the initial integration time.
216      * @return initial integration time
217      */
218     public double getInitialTime() {
219         return initialTime;
220     }
221 
222     /**
223      * Get the final integration time.
224      * @return final integration time
225      */
226     public double getFinalTime() {
227         return finalTime;
228     }
229 
230     /**
231      * Get the state at interpolated time.
232      * @param time time of the interpolated point
233      * @return state at interpolated time
234      */
235     public ODEStateAndDerivative getInterpolatedState(final double time) {
236 
237         // initialize the search with the complete steps table
238         int iMin = 0;
239         final ODEStateInterpolator sMin = steps.get(iMin);
240         double tMin = 0.5 * (sMin.getPreviousState().getTime() + sMin.getCurrentState().getTime());
241 
242         int iMax = steps.size() - 1;
243         final ODEStateInterpolator sMax = steps.get(iMax);
244         double tMax = 0.5 * (sMax.getPreviousState().getTime() + sMax.getCurrentState().getTime());
245 
246         // handle points outside of the integration interval
247         // or in the first and last step
248         if (locatePoint(time, sMin) <= 0) {
249             index = iMin;
250             return sMin.getInterpolatedState(time);
251         }
252         if (locatePoint(time, sMax) >= 0) {
253             index = iMax;
254             return sMax.getInterpolatedState(time);
255         }
256 
257         // reduction of the table slice size
258         while (iMax - iMin > 5) {
259 
260             // use the last estimated index as the splitting index
261             final ODEStateInterpolator si = steps.get(index);
262             final int location = locatePoint(time, si);
263             if (location < 0) {
264                 iMax = index;
265                 tMax = 0.5 * (si.getPreviousState().getTime() + si.getCurrentState().getTime());
266             } else if (location > 0) {
267                 iMin = index;
268                 tMin = 0.5 * (si.getPreviousState().getTime() + si.getCurrentState().getTime());
269             } else {
270                 // we have found the target step, no need to continue searching
271                 return si.getInterpolatedState(time);
272             }
273 
274             // compute a new estimate of the index in the reduced table slice
275             final int iMed = (iMin + iMax) / 2;
276             final ODEStateInterpolator sMed = steps.get(iMed);
277             final double tMed = 0.5 * (sMed.getPreviousState().getTime() + sMed.getCurrentState().getTime());
278 
279             if ((FastMath.abs(tMed - tMin) < 1e-6) || (FastMath.abs(tMax - tMed) < 1e-6)) {
280                 // too close to the bounds, we estimate using a simple dichotomy
281                 index = iMed;
282             } else {
283                 // estimate the index using a reverse quadratic polynom
284                 // (reverse means we have i = P(t), thus allowing to simply
285                 // compute index = P(time) rather than solving a quadratic equation)
286                 final double d12 = tMax - tMed;
287                 final double d23 = tMed - tMin;
288                 final double d13 = tMax - tMin;
289                 final double dt1 = time - tMax;
290                 final double dt2 = time - tMed;
291                 final double dt3 = time - tMin;
292                 final double iLagrange = ((dt2 * dt3 * d23) * iMax -
293                                 (dt1 * dt3 * d13) * iMed +
294                                 (dt1 * dt2 * d12) * iMin) /
295                                 (d12 * d23 * d13);
296                 index = (int) FastMath.rint(iLagrange);
297             }
298 
299             // force the next size reduction to be at least one tenth
300             final int low  = FastMath.max(iMin + 1, (9 * iMin + iMax) / 10);
301             final int high = FastMath.min(iMax - 1, (iMin + 9 * iMax) / 10);
302             if (index < low) {
303                 index = low;
304             } else if (index > high) {
305                 index = high;
306             }
307 
308         }
309 
310         // now the table slice is very small, we perform an iterative search
311         index = iMin;
312         while ((index <= iMax) && (locatePoint(time, steps.get(index)) > 0)) {
313             ++index;
314         }
315 
316         return steps.get(index).getInterpolatedState(time);
317 
318     }
319 
320     /** Compare a step interval and a double.
321      * @param time point to locate
322      * @param interval step interval
323      * @return -1 if the double is before the interval, 0 if it is in
324      * the interval, and +1 if it is after the interval, according to
325      * the interval direction
326      */
327     private int locatePoint(final double time, final ODEStateInterpolator interval) {
328         if (forward) {
329             if (time < interval.getPreviousState().getTime()) {
330                 return -1;
331             } else if (time > interval.getCurrentState().getTime()) {
332                 return +1;
333             } else {
334                 return 0;
335             }
336         }
337         if (time > interval.getPreviousState().getTime()) {
338             return -1;
339         } else if (time < interval.getCurrentState().getTime()) {
340             return +1;
341         } else {
342             return 0;
343         }
344     }
345 
346 }