1 /* Copyright 2002-2023 CS GROUP 2 * Licensed to CS GROUP (CS) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * CS 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 package org.orekit.utils; 18 19 import org.hipparchus.analysis.interpolation.HermiteInterpolator; 20 import org.hipparchus.geometry.euclidean.threed.Rotation; 21 import org.hipparchus.geometry.euclidean.threed.RotationConvention; 22 import org.hipparchus.geometry.euclidean.threed.Vector3D; 23 import org.hipparchus.util.FastMath; 24 import org.hipparchus.util.MathArrays; 25 import org.orekit.errors.OrekitInternalError; 26 import org.orekit.time.AbsoluteDate; 27 import org.orekit.time.AbstractTimeInterpolator; 28 29 import java.util.List; 30 31 /** 32 * Class using Hermite interpolator to interpolate time stamped angular coordinates. 33 * <p> 34 * As this implementation of interpolation is polynomial, it should be used only with small number of interpolation points 35 * (about 10-20 points) in order to avoid <a href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's phenomenon</a> 36 * and numerical problems (including NaN appearing). 37 * 38 * @author Vincent Cucchietti 39 * @author Luc Maisonobe 40 * @see HermiteInterpolator 41 * @see TimeStampedAngularCoordinates 42 */ 43 public class TimeStampedAngularCoordinatesHermiteInterpolator 44 extends AbstractTimeInterpolator<TimeStampedAngularCoordinates> { 45 46 /** Filter for derivatives from the sample to use in interpolation. */ 47 private final AngularDerivativesFilter filter; 48 49 /** 50 * Constructor with : 51 * <ul> 52 * <li>Default number of interpolation points of {@code DEFAULT_INTERPOLATION_POINTS}</li> 53 * <li>Default extrapolation threshold value ({@code DEFAULT_EXTRAPOLATION_THRESHOLD_SEC} s)</li> 54 * <li>Use of angular and first time derivative for attitude interpolation</li> 55 * </ul> 56 * As this implementation of interpolation is polynomial, it should be used only with small number of interpolation 57 * points (about 10-20 points) in order to avoid <a href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's 58 * phenomenon</a> and numerical problems (including NaN appearing). 59 */ 60 public TimeStampedAngularCoordinatesHermiteInterpolator() { 61 this(DEFAULT_INTERPOLATION_POINTS); 62 } 63 64 /** 65 * /** Constructor with : 66 * <ul> 67 * <li>Default extrapolation threshold value ({@code DEFAULT_EXTRAPOLATION_THRESHOLD_SEC} s)</li> 68 * <li>Use of angular and first time derivative for attitude interpolation</li> 69 * </ul> 70 * As this implementation of interpolation is polynomial, it should be used only with small number of interpolation 71 * points (about 10-20 points) in order to avoid <a href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's 72 * phenomenon</a> and numerical problems (including NaN appearing). 73 * 74 * @param interpolationPoints number of interpolation points 75 */ 76 public TimeStampedAngularCoordinatesHermiteInterpolator(final int interpolationPoints) { 77 this(interpolationPoints, AngularDerivativesFilter.USE_RR); 78 } 79 80 /** 81 * Constructor with : 82 * <ul> 83 * <li>Default extrapolation threshold value ({@code DEFAULT_EXTRAPOLATION_THRESHOLD_SEC} s)</li> 84 * </ul> 85 * As this implementation of interpolation is polynomial, it should be used only with small number of interpolation 86 * points (about 10-20 points) in order to avoid <a href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's 87 * phenomenon</a> and numerical problems (including NaN appearing). 88 * 89 * @param interpolationPoints number of interpolation points 90 * @param filter filter for derivatives from the sample to use in interpolation 91 */ 92 public TimeStampedAngularCoordinatesHermiteInterpolator(final int interpolationPoints, 93 final AngularDerivativesFilter filter) { 94 this(interpolationPoints, DEFAULT_EXTRAPOLATION_THRESHOLD_SEC, filter); 95 } 96 97 /** 98 * Constructor. 99 * <p> 100 * As this implementation of interpolation is polynomial, it should be used only with small number of interpolation 101 * points (about 10-20 points) in order to avoid <a href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's 102 * phenomenon</a> and numerical problems (including NaN appearing). 103 * 104 * @param interpolationPoints number of interpolation points 105 * @param extrapolationThreshold extrapolation threshold beyond which the propagation will fail 106 * @param filter filter for derivatives from the sample to use in interpolation 107 */ 108 public TimeStampedAngularCoordinatesHermiteInterpolator(final int interpolationPoints, 109 final double extrapolationThreshold, 110 final AngularDerivativesFilter filter) { 111 super(interpolationPoints, extrapolationThreshold); 112 this.filter = filter; 113 } 114 115 /** Get filter for derivatives from the sample to use in interpolation. 116 * @return filter for derivatives from the sample to use in interpolation 117 */ 118 public AngularDerivativesFilter getFilter() { 119 return filter; 120 } 121 122 /** 123 * {@inheritDoc} 124 * <p> 125 * The interpolated instance is created by polynomial Hermite interpolation on Rodrigues vector ensuring rotation rate 126 * remains the exact derivative of rotation. 127 * <p> 128 * This method is based on Sergei Tanygin's paper <a 129 * href="http://www.agi.com/resources/white-papers/attitude-interpolation">Attitude Interpolation</a>, changing the norm 130 * of the vector to match the modified Rodrigues vector as described in Malcolm D. Shuster's paper <a 131 * href="http://www.ladispe.polito.it/corsi/Meccatronica/02JHCOR/2011-12/Slides/Shuster_Pub_1993h_J_Repsurv_scan.pdf">A 132 * Survey of Attitude Representations</a>. This change avoids the singularity at π. There is still a singularity at 2π, 133 * which is handled by slightly offsetting all rotations when this singularity is detected. Another change is that the 134 * mean linear motion is first removed before interpolation and added back after interpolation. This allows to use 135 * interpolation even when the sample covers much more than one turn and even when sample points are separated by more 136 * than one turn. 137 * </p> 138 * <p> 139 * Note that even if first and second time derivatives (rotation rates and acceleration) from sample can be ignored, the 140 * interpolated instance always includes interpolated derivatives. This feature can be used explicitly to compute these 141 * derivatives when it would be too complex to compute them from an analytical formula: just compute a few sample points 142 * from the explicit formula and set the derivatives to zero in these sample points, then use interpolation to add 143 * derivatives consistent with the rotations. 144 */ 145 @Override 146 protected TimeStampedAngularCoordinates interpolate(final InterpolationData interpolationData) { 147 148 // Get date 149 final AbsoluteDate date = interpolationData.getInterpolationDate(); 150 151 // Get sample 152 final List<TimeStampedAngularCoordinates> sample = interpolationData.getNeighborList(); 153 154 // set up safety elements for 2π singularity avoidance 155 final double epsilon = 2 * FastMath.PI / sample.size(); 156 final double threshold = FastMath.min(-(1.0 - 1.0e-4), -FastMath.cos(epsilon / 4)); 157 158 // set up a linear model canceling mean rotation rate 159 final Vector3D meanRate; 160 Vector3D sum = Vector3D.ZERO; 161 if (filter != AngularDerivativesFilter.USE_R) { 162 for (final TimeStampedAngularCoordinates datedAC : sample) { 163 sum = sum.add(datedAC.getRotationRate()); 164 } 165 meanRate = new Vector3D(1.0 / sample.size(), sum); 166 } 167 else { 168 TimeStampedAngularCoordinates previous = null; 169 for (final TimeStampedAngularCoordinates datedAC : sample) { 170 if (previous != null) { 171 sum = sum.add(TimeStampedAngularCoordinates.estimateRate(previous.getRotation(), datedAC.getRotation(), 172 datedAC.getDate() 173 .durationFrom(previous.getDate()))); 174 } 175 previous = datedAC; 176 } 177 meanRate = new Vector3D(1.0 / (sample.size() - 1), sum); 178 } 179 TimeStampedAngularCoordinates offset = 180 new TimeStampedAngularCoordinates(date, Rotation.IDENTITY, meanRate, Vector3D.ZERO); 181 182 boolean restart = true; 183 for (int i = 0; restart && i < sample.size() + 2; ++i) { 184 185 // offset adaptation parameters 186 restart = false; 187 188 // set up an interpolator taking derivatives into account 189 final HermiteInterpolator interpolator = new HermiteInterpolator(); 190 191 // add sample points 192 double sign = 1.0; 193 Rotation previous = Rotation.IDENTITY; 194 195 for (final TimeStampedAngularCoordinates ac : sample) { 196 197 // remove linear offset from the current coordinates 198 final double dt = ac.getDate().durationFrom(date); 199 final TimeStampedAngularCoordinates fixed = ac.subtractOffset(offset.shiftedBy(dt)); 200 201 // make sure all interpolated points will be on the same branch 202 final double dot = MathArrays.linearCombination(fixed.getRotation().getQ0(), previous.getQ0(), 203 fixed.getRotation().getQ1(), previous.getQ1(), 204 fixed.getRotation().getQ2(), previous.getQ2(), 205 fixed.getRotation().getQ3(), previous.getQ3()); 206 sign = FastMath.copySign(1.0, dot * sign); 207 previous = fixed.getRotation(); 208 209 // check modified Rodrigues vector singularity 210 if (fixed.getRotation().getQ0() * sign < threshold) { 211 // the sample point is close to a modified Rodrigues vector singularity 212 // we need to change the linear offset model to avoid this 213 restart = true; 214 break; 215 } 216 217 final double[][] rodrigues = fixed.getModifiedRodrigues(sign); 218 switch (filter) { 219 case USE_RRA: 220 // populate sample with rotation, rotation rate and acceleration data 221 interpolator.addSamplePoint(dt, rodrigues[0], rodrigues[1], rodrigues[2]); 222 break; 223 case USE_RR: 224 // populate sample with rotation and rotation rate data 225 interpolator.addSamplePoint(dt, rodrigues[0], rodrigues[1]); 226 break; 227 case USE_R: 228 // populate sample with rotation data only 229 interpolator.addSamplePoint(dt, rodrigues[0]); 230 break; 231 default: 232 // this should never happen 233 throw new OrekitInternalError(null); 234 } 235 } 236 237 if (restart) { 238 // interpolation failed, some intermediate rotation was too close to 2π 239 // we need to offset all rotations to avoid the singularity 240 offset = offset.addOffset(new AngularCoordinates(new Rotation(Vector3D.PLUS_I, 241 epsilon, 242 RotationConvention.VECTOR_OPERATOR), 243 Vector3D.ZERO, Vector3D.ZERO)); 244 } 245 else { 246 // interpolation succeeded with the current offset 247 final double[][] p = interpolator.derivatives(0.0, 2); 248 final AngularCoordinates ac = AngularCoordinates.createFromModifiedRodrigues(p); 249 return new TimeStampedAngularCoordinates(offset.getDate(), 250 ac.getRotation(), 251 ac.getRotationRate(), 252 ac.getRotationAcceleration()).addOffset(offset); 253 } 254 255 } 256 257 // this should never happen 258 throw new OrekitInternalError(null); 259 } 260 }