001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.math3.ode; 019 020import java.util.ArrayList; 021import java.util.List; 022 023import org.apache.commons.math3.RealFieldElement; 024import org.apache.commons.math3.exception.DimensionMismatchException; 025import org.apache.commons.math3.exception.MathIllegalArgumentException; 026import org.apache.commons.math3.exception.MaxCountExceededException; 027import org.apache.commons.math3.exception.util.LocalizedFormats; 028import org.apache.commons.math3.ode.sampling.FieldStepHandler; 029import org.apache.commons.math3.ode.sampling.FieldStepInterpolator; 030import org.apache.commons.math3.util.FastMath; 031 032/** 033 * This class stores all information provided by an ODE integrator 034 * during the integration process and build a continuous model of the 035 * solution from this. 036 * 037 * <p>This class act as a step handler from the integrator point of 038 * view. It is called iteratively during the integration process and 039 * stores a copy of all steps information in a sorted collection for 040 * later use. Once the integration process is over, the user can use 041 * the {@link #getInterpolatedState(RealFieldElement) getInterpolatedState} 042 * method to retrieve this information at any time. It is important to wait 043 * for the integration to be over before attempting to call {@link 044 * #getInterpolatedState(RealFieldElement)} because some internal 045 * variables are set only once the last step has been handled.</p> 046 * 047 * <p>This is useful for example if the main loop of the user 048 * application should remain independent from the integration process 049 * or if one needs to mimic the behaviour of an analytical model 050 * despite a numerical model is used (i.e. one needs the ability to 051 * get the model value at any time or to navigate through the 052 * data).</p> 053 * 054 * <p>If problem modeling is done with several separate 055 * integration phases for contiguous intervals, the same 056 * ContinuousOutputModel can be used as step handler for all 057 * integration phases as long as they are performed in order and in 058 * the same direction. As an example, one can extrapolate the 059 * trajectory of a satellite with one model (i.e. one set of 060 * differential equations) up to the beginning of a maneuver, use 061 * another more complex model including thrusters modeling and 062 * accurate attitude control during the maneuver, and revert to the 063 * first model after the end of the maneuver. If the same continuous 064 * output model handles the steps of all integration phases, the user 065 * do not need to bother when the maneuver begins or ends, he has all 066 * the data available in a transparent manner.</p> 067 * 068 * <p>One should be aware that the amount of data stored in a 069 * ContinuousOutputFieldModel instance can be important if the state vector 070 * is large, if the integration interval is long or if the steps are 071 * small (which can result from small tolerance settings in {@link 072 * org.apache.commons.math3.ode.nonstiff.AdaptiveStepsizeFieldIntegrator adaptive 073 * step size integrators}).</p> 074 * 075 * @see FieldStepHandler 076 * @see FieldStepInterpolator 077 * @param <T> the type of the field elements 078 * @since 3.6 079 */ 080 081public class ContinuousOutputFieldModel<T extends RealFieldElement<T>> 082 implements FieldStepHandler<T> { 083 084 /** Initial integration time. */ 085 private T initialTime; 086 087 /** Final integration time. */ 088 private T finalTime; 089 090 /** Integration direction indicator. */ 091 private boolean forward; 092 093 /** Current interpolator index. */ 094 private int index; 095 096 /** Steps table. */ 097 private List<FieldStepInterpolator<T>> steps; 098 099 /** Simple constructor. 100 * Build an empty continuous output model. 101 */ 102 public ContinuousOutputFieldModel() { 103 steps = new ArrayList<FieldStepInterpolator<T>>(); 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.size() == 0) { 124 return; 125 } 126 127 if (steps.size() == 0) { 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 157 for (FieldStepInterpolator<T> interpolator : model.steps) { 158 steps.add(interpolator); 159 } 160 161 index = steps.size() - 1; 162 finalTime = (steps.get(index)).getCurrentState().getTime(); 163 164 } 165 166 /** Check dimensions equality. 167 * @param d1 first dimension 168 * @param d2 second dimansion 169 * @exception DimensionMismatchException if dimensions do not match 170 */ 171 private void checkDimensionsEquality(final int d1, final int d2) 172 throws DimensionMismatchException { 173 if (d1 != d2) { 174 throw new DimensionMismatchException(d2, d1); 175 } 176 } 177 178 /** {@inheritDoc} */ 179 public void init(final FieldODEStateAndDerivative<T> initialState, final T t) { 180 initialTime = initialState.getTime(); 181 finalTime = t; 182 forward = true; 183 index = 0; 184 steps.clear(); 185 } 186 187 /** Handle the last accepted step. 188 * A copy of the information provided by the last step is stored in 189 * the instance for later use. 190 * @param interpolator interpolator for the last accepted step. 191 * @param isLast true if the step is the last one 192 * @exception MaxCountExceededException if the number of functions evaluations is exceeded 193 * during step finalization 194 */ 195 public void handleStep(final FieldStepInterpolator<T> interpolator, final boolean isLast) 196 throws MaxCountExceededException { 197 198 if (steps.size() == 0) { 199 initialTime = interpolator.getPreviousState().getTime(); 200 forward = interpolator.isForward(); 201 } 202 203 steps.add(interpolator); 204 205 if (isLast) { 206 finalTime = interpolator.getCurrentState().getTime(); 207 index = steps.size() - 1; 208 } 209 210 } 211 212 /** 213 * Get the initial integration time. 214 * @return initial integration time 215 */ 216 public T getInitialTime() { 217 return initialTime; 218 } 219 220 /** 221 * Get the final integration time. 222 * @return final integration time 223 */ 224 public T getFinalTime() { 225 return finalTime; 226 } 227 228 /** 229 * Get the state at interpolated time. 230 * @param time time of the interpolated point 231 * @return state at interpolated time 232 */ 233 public FieldODEStateAndDerivative<T> getInterpolatedState(final T time) { 234 235 // initialize the search with the complete steps table 236 int iMin = 0; 237 final FieldStepInterpolator<T> sMin = steps.get(iMin); 238 T tMin = sMin.getPreviousState().getTime().add(sMin.getCurrentState().getTime()).multiply(0.5); 239 240 int iMax = steps.size() - 1; 241 final FieldStepInterpolator<T> sMax = steps.get(iMax); 242 T tMax = sMax.getPreviousState().getTime().add(sMax.getCurrentState().getTime()).multiply(0.5); 243 244 // handle points outside of the integration interval 245 // or in the first and last step 246 if (locatePoint(time, sMin) <= 0) { 247 index = iMin; 248 return sMin.getInterpolatedState(time); 249 } 250 if (locatePoint(time, sMax) >= 0) { 251 index = iMax; 252 return sMax.getInterpolatedState(time); 253 } 254 255 // reduction of the table slice size 256 while (iMax - iMin > 5) { 257 258 // use the last estimated index as the splitting index 259 final FieldStepInterpolator<T> si = steps.get(index); 260 final int location = locatePoint(time, si); 261 if (location < 0) { 262 iMax = index; 263 tMax = si.getPreviousState().getTime().add(si.getCurrentState().getTime()).multiply(0.5); 264 } else if (location > 0) { 265 iMin = index; 266 tMin = si.getPreviousState().getTime().add(si.getCurrentState().getTime()).multiply(0.5); 267 } else { 268 // we have found the target step, no need to continue searching 269 return si.getInterpolatedState(time); 270 } 271 272 // compute a new estimate of the index in the reduced table slice 273 final int iMed = (iMin + iMax) / 2; 274 final FieldStepInterpolator<T> sMed = steps.get(iMed); 275 final T tMed = sMed.getPreviousState().getTime().add(sMed.getCurrentState().getTime()).multiply(0.5); 276 277 if (tMed.subtract(tMin).abs().subtract(1.0e-6).getReal() < 0 || 278 tMax.subtract(tMed).abs().subtract(1.0e-6).getReal() < 0) { 279 // too close to the bounds, we estimate using a simple dichotomy 280 index = iMed; 281 } else { 282 // estimate the index using a reverse quadratic polynomial 283 // (reverse means we have i = P(t), thus allowing to simply 284 // compute index = P(time) rather than solving a quadratic equation) 285 final T d12 = tMax.subtract(tMed); 286 final T d23 = tMed.subtract(tMin); 287 final T d13 = tMax.subtract(tMin); 288 final T dt1 = time.subtract(tMax); 289 final T dt2 = time.subtract(tMed); 290 final T dt3 = time.subtract(tMin); 291 final T iLagrange = dt2.multiply(dt3).multiply(d23).multiply(iMax). 292 subtract(dt1.multiply(dt3).multiply(d13).multiply(iMed)). 293 add( dt1.multiply(dt2).multiply(d12).multiply(iMin)). 294 divide(d12.multiply(d23).multiply(d13)); 295 index = (int) FastMath.rint(iLagrange.getReal()); 296 } 297 298 // force the next size reduction to be at least one tenth 299 final int low = FastMath.max(iMin + 1, (9 * iMin + iMax) / 10); 300 final int high = FastMath.min(iMax - 1, (iMin + 9 * iMax) / 10); 301 if (index < low) { 302 index = low; 303 } else if (index > high) { 304 index = high; 305 } 306 307 } 308 309 // now the table slice is very small, we perform an iterative search 310 index = iMin; 311 while (index <= iMax && locatePoint(time, steps.get(index)) > 0) { 312 ++index; 313 } 314 315 return steps.get(index).getInterpolatedState(time); 316 317 } 318 319 /** Compare a step interval and a double. 320 * @param time point to locate 321 * @param interval step interval 322 * @return -1 if the double is before the interval, 0 if it is in 323 * the interval, and +1 if it is after the interval, according to 324 * the interval direction 325 */ 326 private int locatePoint(final T time, final FieldStepInterpolator<T> interval) { 327 if (forward) { 328 if (time.subtract(interval.getPreviousState().getTime()).getReal() < 0) { 329 return -1; 330 } else if (time.subtract(interval.getCurrentState().getTime()).getReal() > 0) { 331 return +1; 332 } else { 333 return 0; 334 } 335 } 336 if (time.subtract(interval.getPreviousState().getTime()).getReal() > 0) { 337 return -1; 338 } else if (time.subtract(interval.getCurrentState().getTime()).getReal() < 0) { 339 return +1; 340 } else { 341 return 0; 342 } 343 } 344 345}