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.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 }