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 }