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 }