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