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.util.ArrayList;
21  import java.util.List;
22  
23  import org.apache.commons.math4.legacy.core.RealFieldElement;
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.FieldStepHandler;
29  import org.apache.commons.math4.legacy.ode.sampling.FieldStepInterpolator;
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 #getInterpolatedState(RealFieldElement) getInterpolatedState}
42   * method to retrieve this information at any time. It is important to wait
43   * for the integration to be over before attempting to call {@link
44   * #getInterpolatedState(RealFieldElement)} because some internal
45   * variables are set only once the last step has been handled.</p>
46   *
47   * <p>This is useful for example if the main loop of the user
48   * application should remain independent from the integration process
49   * or if one needs to mimic the behaviour of an analytical model
50   * despite a numerical model is used (i.e. one needs the ability to
51   * get the model value at any time or to navigate through the
52   * data).</p>
53   *
54   * <p>If problem modeling is done with several separate
55   * integration phases for contiguous intervals, the same
56   * ContinuousOutputModel can be used as step handler for all
57   * integration phases as long as they are performed in order and in
58   * the same direction. As an example, one can extrapolate the
59   * trajectory of a satellite with one model (i.e. one set of
60   * differential equations) up to the beginning of a maneuver, use
61   * another more complex model including thrusters modeling and
62   * accurate attitude control during the maneuver, and revert to the
63   * first model after the end of the maneuver. If the same continuous
64   * output model handles the steps of all integration phases, the user
65   * do not need to bother when the maneuver begins or ends, he has all
66   * the data available in a transparent manner.</p>
67   *
68   * <p>One should be aware that the amount of data stored in a
69   * ContinuousOutputFieldModel instance can be important if the state vector
70   * is large, if the integration interval is long or if the steps are
71   * small (which can result from small tolerance settings in {@link
72   * org.apache.commons.math4.legacy.ode.nonstiff.AdaptiveStepsizeFieldIntegrator adaptive
73   * step size integrators}).</p>
74   *
75   * @see FieldStepHandler
76   * @see FieldStepInterpolator
77   * @param <T> the type of the field elements
78   * @since 3.6
79   */
80  
81  public class ContinuousOutputFieldModel<T extends RealFieldElement<T>>
82      implements FieldStepHandler<T> {
83  
84      /** Initial integration time. */
85      private T initialTime;
86  
87      /** Final integration time. */
88      private T finalTime;
89  
90      /** Integration direction indicator. */
91      private boolean forward;
92  
93      /** Current interpolator index. */
94      private int index;
95  
96      /** Steps table. */
97      private List<FieldStepInterpolator<T>> steps;
98  
99      /** Simple constructor.
100      * Build an empty continuous output model.
101      */
102     public ContinuousOutputFieldModel() {
103         steps       = new ArrayList<>();
104         initialTime = null;
105         finalTime   = null;
106         forward     = true;
107         index       = 0;
108     }
109 
110     /** Append another model at the end of the instance.
111      * @param model model to add at the end of the instance
112      * @exception MathIllegalArgumentException if the model to append is not
113      * compatible with the instance (dimension of the state vector,
114      * propagation direction, hole between the dates)
115      * @exception DimensionMismatchException if the dimensions of the states or
116      * the number of secondary states do not match
117      * @exception MaxCountExceededException if the number of functions evaluations is exceeded
118      * during step finalization
119      */
120     public void append(final ContinuousOutputFieldModel<T> model)
121         throws MathIllegalArgumentException, MaxCountExceededException {
122 
123         if (model.steps.isEmpty()) {
124             return;
125         }
126 
127         if (steps.isEmpty()) {
128             initialTime = model.initialTime;
129             forward     = model.forward;
130         } else {
131 
132             // safety checks
133             final FieldODEStateAndDerivative<T> s1 = steps.get(0).getPreviousState();
134             final FieldODEStateAndDerivative<T> s2 = model.steps.get(0).getPreviousState();
135             checkDimensionsEquality(s1.getStateDimension(), s2.getStateDimension());
136             checkDimensionsEquality(s1.getNumberOfSecondaryStates(), s2.getNumberOfSecondaryStates());
137             for (int i = 0; i < s1.getNumberOfSecondaryStates(); ++i) {
138                 checkDimensionsEquality(s1.getSecondaryStateDimension(i), s2.getSecondaryStateDimension(i));
139             }
140 
141             if (forward ^ model.forward) {
142                 throw new MathIllegalArgumentException(LocalizedFormats.PROPAGATION_DIRECTION_MISMATCH);
143             }
144 
145             final FieldStepInterpolator<T> lastInterpolator = steps.get(index);
146             final T current  = lastInterpolator.getCurrentState().getTime();
147             final T previous = lastInterpolator.getPreviousState().getTime();
148             final T step = current.subtract(previous);
149             final T gap = model.getInitialTime().subtract(current);
150             if (gap.abs().subtract(step.abs().multiply(1.0e-3)).getReal() > 0) {
151                 throw new MathIllegalArgumentException(LocalizedFormats.HOLE_BETWEEN_MODELS_TIME_RANGES,
152                                                        gap.abs().getReal());
153             }
154         }
155 
156         steps.addAll(model.steps);
157 
158         index = steps.size() - 1;
159         finalTime = (steps.get(index)).getCurrentState().getTime();
160     }
161 
162     /** Check dimensions equality.
163      * @param d1 first dimension
164      * @param d2 second dimension
165      * @exception DimensionMismatchException if dimensions do not match
166      */
167     private void checkDimensionsEquality(final int d1, final int d2)
168         throws DimensionMismatchException {
169         if (d1 != d2) {
170             throw new DimensionMismatchException(d2, d1);
171         }
172     }
173 
174     /** {@inheritDoc} */
175     @Override
176     public void init(final FieldODEStateAndDerivative<T> initialState, final T t) {
177         initialTime = initialState.getTime();
178         finalTime   = t;
179         forward     = true;
180         index       = 0;
181         steps.clear();
182     }
183 
184     /** Handle the last accepted step.
185      * A copy of the information provided by the last step is stored in
186      * the instance for later use.
187      * @param interpolator interpolator for the last accepted step.
188      * @param isLast true if the step is the last one
189      * @exception MaxCountExceededException if the number of functions evaluations is exceeded
190      * during step finalization
191      */
192     @Override
193     public void handleStep(final FieldStepInterpolator<T> interpolator, final boolean isLast)
194         throws MaxCountExceededException {
195 
196         if (steps.isEmpty()) {
197             initialTime = interpolator.getPreviousState().getTime();
198             forward     = interpolator.isForward();
199         }
200 
201         steps.add(interpolator);
202 
203         if (isLast) {
204             finalTime = interpolator.getCurrentState().getTime();
205             index     = steps.size() - 1;
206         }
207     }
208 
209     /**
210      * Get the initial integration time.
211      * @return initial integration time
212      */
213     public T getInitialTime() {
214         return initialTime;
215     }
216 
217     /**
218      * Get the final integration time.
219      * @return final integration time
220      */
221     public T getFinalTime() {
222         return finalTime;
223     }
224 
225     /**
226      * Get the state at interpolated time.
227      * @param time time of the interpolated point
228      * @return state at interpolated time
229      */
230     public FieldODEStateAndDerivative<T> getInterpolatedState(final T time) {
231 
232         // initialize the search with the complete steps table
233         int iMin = 0;
234         final FieldStepInterpolator<T> sMin = steps.get(iMin);
235         T tMin = sMin.getPreviousState().getTime().add(sMin.getCurrentState().getTime()).multiply(0.5);
236 
237         int iMax = steps.size() - 1;
238         final FieldStepInterpolator<T> sMax = steps.get(iMax);
239         T tMax = sMax.getPreviousState().getTime().add(sMax.getCurrentState().getTime()).multiply(0.5);
240 
241         // handle points outside of the integration interval
242         // or in the first and last step
243         if (locatePoint(time, sMin) <= 0) {
244             index = iMin;
245             return sMin.getInterpolatedState(time);
246         }
247         if (locatePoint(time, sMax) >= 0) {
248             index = iMax;
249             return sMax.getInterpolatedState(time);
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             final FieldStepInterpolator<T> si = steps.get(index);
257             final int location = locatePoint(time, si);
258             if (location < 0) {
259                 iMax = index;
260                 tMax = si.getPreviousState().getTime().add(si.getCurrentState().getTime()).multiply(0.5);
261             } else if (location > 0) {
262                 iMin = index;
263                 tMin = si.getPreviousState().getTime().add(si.getCurrentState().getTime()).multiply(0.5);
264             } else {
265                 // we have found the target step, no need to continue searching
266                 return si.getInterpolatedState(time);
267             }
268 
269             // compute a new estimate of the index in the reduced table slice
270             final int iMed = (iMin + iMax) / 2;
271             final FieldStepInterpolator<T> sMed = steps.get(iMed);
272             final T tMed = sMed.getPreviousState().getTime().add(sMed.getCurrentState().getTime()).multiply(0.5);
273 
274             if (tMed.subtract(tMin).abs().subtract(1.0e-6).getReal() < 0 ||
275                 tMax.subtract(tMed).abs().subtract(1.0e-6).getReal() < 0) {
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 polynomial
280                 // (reverse means we have i = P(t), thus allowing to simply
281                 // compute index = P(time) rather than solving a quadratic equation)
282                 final T d12 = tMax.subtract(tMed);
283                 final T d23 = tMed.subtract(tMin);
284                 final T d13 = tMax.subtract(tMin);
285                 final T dt1 = time.subtract(tMax);
286                 final T dt2 = time.subtract(tMed);
287                 final T dt3 = time.subtract(tMin);
288                 final T iLagrange =           dt2.multiply(dt3).multiply(d23).multiply(iMax).
289                                      subtract(dt1.multiply(dt3).multiply(d13).multiply(iMed)).
290                                      add(     dt1.multiply(dt2).multiply(d12).multiply(iMin)).
291                                      divide(d12.multiply(d23).multiply(d13));
292                 index = (int) JdkMath.rint(iLagrange.getReal());
293             }
294 
295             // force the next size reduction to be at least one tenth
296             final int low  = JdkMath.max(iMin + 1, (9 * iMin + iMax) / 10);
297             final int high = JdkMath.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         // now the table slice is very small, we perform an iterative search
306         index = iMin;
307         while (index <= iMax && locatePoint(time, steps.get(index)) > 0) {
308             ++index;
309         }
310 
311         return steps.get(index).getInterpolatedState(time);
312     }
313 
314     /** Compare a step interval and a double.
315      * @param time point to locate
316      * @param interval step interval
317      * @return -1 if the double is before the interval, 0 if it is in
318      * the interval, and +1 if it is after the interval, according to
319      * the interval direction
320      */
321     private int locatePoint(final T time, final FieldStepInterpolator<T> interval) {
322         if (forward) {
323             if (time.subtract(interval.getPreviousState().getTime()).getReal() < 0) {
324                 return -1;
325             } else if (time.subtract(interval.getCurrentState().getTime()).getReal() > 0) {
326                 return +1;
327             } else {
328                 return 0;
329             }
330         }
331         if (time.subtract(interval.getPreviousState().getTime()).getReal() > 0) {
332             return -1;
333         } else if (time.subtract(interval.getCurrentState().getTime()).getReal() < 0) {
334             return +1;
335         } else {
336             return 0;
337         }
338     }
339 }