Discrepancy between EME2000 and TOD numerical propagation (with Inertial Forces)

Hello Orekit developers and users,

I have a doubt regarding numerical propagation performed in the TOD frame, and I would appreciate your help to understand whether my approach is correct.

I compared three numerical propagations of the same LEO spacecraft:

  1. A propagation performed in the inertial frame EME2000.
  2. A propagation performed directly in the TOD frame, then transformed back to EME2000 for comparison.

The initial state transformation between the two frames is consistent:

Initial TOD-EME2000 position error = 1.2 e-8 m

However, for a 7-days propagation window, I observe a significant discrepancy between the two solutions.

The TOD frame is generated using:

FramesFactory.getTOD(IERSConventions.IERS_2010 , true);

I tested the propagation both with and without adding InertialForces to the TOD case.

I used Orekit 12.2 version, adding only the Earth gravity field in the force model:

HolmesFeatherstoneAttractionModel(reference frame: ITRF, degree = 36, order = 36)

After 7 days, I obtain the following position errors:

  • TOD - EME2000 = 55.370953 m
  • (TOD + InertialForces) - EME2000 = 47.254541 m
  • (TOD + InertialForces) - TOD = 20.588348 m

Since TOD is a non-inertial frame, my understanding is that fictitious accelerations (Coriolis, centrifugal and Euler terms) should be added when integrating the equations of motion in this frame.

For this reason, I added:

new InertialForces(Frames Factory.getEME2000())

to the TOD propagation.

However, the discrepancy remains at the level of several tens of meters after one week.

I also manually computed the fictitious acceleration terms and compared them with the acceleration provided by the Orekit InertialForces model. The two results are identical, so I believe that the implementation of InertialForces is not the source of the discrepancy.

In particular, I would like to understand:

  1. Is InertialForces required when propagating in TOD, even if the frame is considered pseudo-inertial by Orekit?
    The fictitious contributions are very small, but I would like to understand the theoretically correct approach.
  2. Which are the sources of these residual errors?

Any suggestion or clarification would be greatly appreciated.

Best regards,
Daniele

I am not really sure what is going on, but I am pretty sure it is a rabbit hole.
You are right the TOD frame is not as inertial as the EME2000 frame, but the differences are only linked to rotation terms as both frames share the same origin at the center of the Earth. I think that when we implemented the inertial forces, we mainly focused on frames that have different origins, and we tried to compensate for the relative acceleration between these origins. With this in mind, the most inertial frame we can think of is centered at the Solar system barycenter, and Earth-based frames take into account Earth-Moon barycenter motion plus Earth motion wrt. Earth-Moon barycenter. However, there is a trap here: third body acceleration. Third-body acceleration is computed as a relative acceleration induced by the third body (i.e. Sun or Moon) to both spacecraft and Earth. So what third-body acceleration does is basically try to compensate for inertial accelerations too. At the end, if we use both third-body acceleration and inertial forces, we add the same effect twice. And this is only when considering the different origins of frames, not their rotation rate.

So I think inertial forces is correct in computing what it was intended to compute, but I am not sure it is the effect you need, and I think the third-body trap lurking around makes things even more difficult to understand.

At the end, I have no answer, and if you dig deeper, you will find yourself diving into the rabbit hole :scream:

Hi @luc,

I found that Orekit’s InertialForces formula is correct, but the transform it receives is incomplete. In Orekit 13.1, the scalar MODProvider.getTransform(AbsoluteDate) returns the time-dependent precession rotation without its angular rate and angular acceleration. TOD is built through MOD, so the initial EME2000→TOD PVA transformation and every subsequent InertialForces evaluation miss the same derivatives. The missing initial GEO velocity contribution is about 3.258e-4 m/s.

Correcting only this initial PVA is not sufficient because the derivatives are also required throughout propagation.

The essential Orekit-side change is small (imports omitted):


- final double[] angles = precessionFunction.value(date);

- final Rotation precession = r4.compose(
-         new Rotation(RotationOrder.ZXZ, RotationConvention.FRAME_TRANSFORM,
-         -angles[0], -angles[1], angles[2]),
-         RotationConvention.FRAME_TRANSFORM);

- return new Transform(date, precession);

+ final FieldAbsoluteDate<UnivariateDerivative2> derivativeDate =
+         new FieldAbsoluteDate<>(UnivariateDerivative2Field.getInstance(), date)
+                 .shiftedBy(new UnivariateDerivative2(0.0, 1.0, 0.0));

+ final UnivariateDerivative2[] angles = precessionFunction.value(derivativeDate);

+ final FieldRotation<UnivariateDerivative2> derivativeR4 =
+         new FieldRotation<>(UnivariateDerivative2Field.getInstance(), r4);

+ final FieldRotation<UnivariateDerivative2> precession = derivativeR4.compose(
+         new FieldRotation<>(RotationOrder.ZXZ, RotationConvention.FRAME_TRANSFORM,
+                             angles[0].negate(), angles[1].negate(), angles[2]),
+         RotationConvention.FRAME_TRANSFORM);

+ return new Transform(date, new AngularCoordinates(precession));

My application-side CompleteInertialForces applies this derivative recovery
only to MOD precession, composes Orekit’s normal MOD→TOD transform, and otherwise
uses the same Coriolis, centrifugal, Euler and translational formulas as
InertialForces.

Results with Cartesian propagation and 36Ă—36 gravity:

  • GEO, seven-day fixed-step RK4 (10 s): 196.759 m with Orekit versus 0.0038 m corrected.

  • LEO (a=7000 km, e=0.001, i=51.6 deg), seven days: 29.971 m with Orekit versus 0.0035 m corrected.

  • Correcting only the initial PVA gives 112.753 m in the LEO case.

The optimized workaround used essentially the same integration steps/evaluations and was about 8–9% slower in the LEO timing. Propagating in EME2000 and transforming only the output remains the no-workaround-overhead option. No third-body forces were included.

I have also implemented a more general method to retrieve the full transform, you can find it at the end of this post. The drawbacks is that the propagation takes 1.6x time as the original one.

NOTE: this problem is also highlighted in the classic .getPVCoordinates(Frame) method, as expected I might add since it use the same Frame transform methods that don’t include the all the terms needed.

Here the graph with the overall results for a GEO orbit, I have also tested a LEO satellite and the results are good as well.

Here my more general implementation, the completeTransform is where the “magic” happens:


    @Override
    public Vector3D acceleration(final SpacecraftState state, final double[] parameters) {
        // During propagation these are the coordinate (fictitious) terms needed
        // when the equations are integrated in state.getFrame().
        final Transform transform = forceTransform(state.getFrame(), state.getDate());
        final Vector3D omega = transform.getRotationRate();
        final Vector3D position = state.getPosition();
        final Vector3D velocity = state.getPVCoordinates().getVelocity();
        return transform.getRotation().applyTo(transform.getAcceleration())
                .add(Vector3D.crossProduct(omega, velocity).scalarMultiply(-2.0))
                .subtract(Vector3D.crossProduct(omega, Vector3D.crossProduct(omega, position)))
                .subtract(Vector3D.crossProduct(transform.getRotationAcceleration(), position));
    }

    @Override
    public <T extends CalculusFieldElement<T>> FieldVector3D<T> acceleration(
            final FieldSpacecraftState<T> state, final T[] parameters) {
        final FieldTransform<FieldUnivariateDerivative2<T>> transform =
                referenceInertialFrame.getTransformTo(state.getFrame(), state.getDate().toFUD2Field());
        final FieldPVCoordinates<T> cartesian = new FieldPVCoordinates<>(transform.getCartesian().getPosition());
        final FieldAngularCoordinates<T> angular = new FieldAngularCoordinates<>(transform.getRotation());
        final FieldVector3D<T> omega = angular.getRotationRate();
        final FieldVector3D<T> position = state.getPosition();
        final FieldVector3D<T> velocity = state.getPVCoordinates().getVelocity();
        return angular.getRotation().applyTo(cartesian.getAcceleration())
                .add(FieldVector3D.crossProduct(omega, velocity).scalarMultiply(-2.0))
                .subtract(FieldVector3D.crossProduct(omega, FieldVector3D.crossProduct(omega, position)))
                .subtract(FieldVector3D.crossProduct(angular.getRotationAcceleration(), position));
    }

    @Override
    public List<ParameterDriver> getParametersDrivers() {
        return Collections.emptyList();
    }

    private Transform completeTransform(final Frame targetFrame, final AbsoluteDate date) {
        final FieldAbsoluteDate<UnivariateDerivative2> derivativeDate =
                new FieldAbsoluteDate<>(UnivariateDerivative2Field.getInstance(), date)
                        .shiftedBy(new UnivariateDerivative2(0.0, 1.0, 0.0));
        final FieldTransform<UnivariateDerivative2> transform =
                referenceInertialFrame.getTransformTo(targetFrame, derivativeDate);
        return new Transform(date,
                new PVCoordinates(transform.getCartesian().getPosition()),
                new AngularCoordinates(transform.getRotation()));
    }

In conclusion, I wonder if the best path forward is to open an issue on GitLab. @luc , what do you think ?

Thanks,
@DDega

Sure, you can open an issue, and provide the fix too!

Good job!

Nice catch!

Could we include it for 13.1.8 patch?

Bryan

Hi everyone, I have a small follow-up.

The missing derivatives are not limited to MODProvider. EclipticProvider has the same pattern: it evaluates the time-dependent mean obliquity but returns a scalar Transform containing only the rotation. Since the Ecliptic frame is not wrapped by a ShiftingTransformProvider, its angular rate and angular acceleration remain zero.

For a point approximately 42,000 km from the origin, I measured an omitted Ecliptic-frame velocity contribution of about 5.58e-7 m/s. I therefore intend to include both MODProvider and EclipticProvider in the library-level correction. I have opened the issue 1996 for those fixes.

I also found related cases in the JPL celestial-body frames. The “inertially oriented” frames use time-dependent IAU pole and node directions but return a bare rotation. For the same test vector, the missing rotational velocity was approximately:

Mars:     2.51e-5 m/s
Moon:     7.06e-3 m/s
Jupiter:  2.27e-6 m/s

The JPL body-oriented frames provide an angular rate, but no angular acceleration. This is relevant for models such as the Moon, whose prime-meridian angle contains nonlinear and periodic terms.

I think it is better to cover it in a separate issue.

Hi,

I continued investigating the EME2000/TOD propagation comparison discussed here. After accounting for the missing MOD precession angular derivatives, I found a second and apparently independent issue when atmospheric drag is enabled.

The remaining discrepancy seems related to how NRLMSISE00 evaluates density and atmospheric velocity when the same physical state is expressed in EME2000 or TOD. Since this is distinct from the original InertialForces discussion, I opened a separate thread here:

Frame-dependence in NRLMSISE00 density and drag acceleration

In summary, before applying a body-frame atmosphere adapter, adding drag increased the seven-day LEO EME2000/TOD position difference from approximately 0.2 m to 15.3 m. The same physical ITRF position produced slightly different NRLMSISE00 densities depending on whether the input position was supplied in EME2000 or TOD.

Thank you,

Daniele