Question on attitude provider with fixed rate

Good morning,

I have a doubt on some results I’m obtaining while renning the following code:

        // Orbit
        double a = 6860176.23765572;
        double e = 0.0010160613326433334;
        double i = 1.7044014447593043;
        double om = 1.439233350035424;
        double OM = 1.3884502116745805;
        double th = 1.1625681691480643;

        AbsoluteDate startingEpoch = new AbsoluteDate(2022, 01, 01, 12, 00, 0.0, TimeScalesFactory.getTAI());
        AbsoluteDate finalEpoch = new AbsoluteDate(2022, 01, 03, 12, 00, 0.0, TimeScalesFactory.getTAI());

        Orbit keplerianOrbit = new KeplerianOrbit(a, e, i, om, OM, th, PositionAngle.TRUE, FramesFactory.getEME2000(), startingEpoch, Constants.IERS2010_EARTH_MU);

        Attitude initAttitude = new Attitude(startingEpoch,
                                             FramesFactory.getEME2000(),
                                             new Rotation(RotationOrder.XYZ, RotationConvention.FRAME_TRANSFORM, 0.0, 0.0, 0.0),
                                             new Vector3D(10 * Math.PI / 180, 20 * Math.PI / 180, 30 * Math.PI / 180),
                                             new Vector3D(0, 0, 0));

        System.out.println("first rot angle = " + initAttitude.getRotation().getAngles(RotationOrder.XYZ, RotationConvention.FRAME_TRANSFORM)[0]);
        System.out.println("second rot angle = " + initAttitude.getRotation().getAngles(RotationOrder.XYZ, RotationConvention.FRAME_TRANSFORM)[1]);
        System.out.println("third rot angle = " + initAttitude.getRotation().getAngles(RotationOrder.XYZ, RotationConvention.FRAME_TRANSFORM)[2]);
        System.out.println("init spin in sat = " + initAttitude.getSpin());
        Transform initTransform = new Transform(startingEpoch, initAttitude.getRotation());
        System.out.println("init spin in EME2000 = " + initTransform.transformVector(initAttitude.getSpin()));
        System.out.println("----------");
        
        // Propagator
        NumericalPropagator prop = new NumericalPropagator(new ClassicalRungeKuttaIntegrator(0.5));
        prop.setInitialState(new SpacecraftState(keplerianOrbit));
        prop.setAttitudeProvider(new FixedRate(initAttitude));

        // Propagate
        SpacecraftState finSpacecraftState = prop.propagate(finalEpoch);
        Attitude finAttitude = finSpacecraftState.getAttitude();
        System.out.println("first rot angle = " + finAttitude.getRotation().getAngles(RotationOrder.XYZ, RotationConvention.FRAME_TRANSFORM)[0]);
        System.out.println("second rot angle = " + finAttitude.getRotation().getAngles(RotationOrder.XYZ, RotationConvention.FRAME_TRANSFORM)[1]);
        System.out.println("third rot angle = " + finAttitude.getRotation().getAngles(RotationOrder.XYZ, RotationConvention.FRAME_TRANSFORM)[2]);
        System.out.println("fin spin in sat = " + finAttitude.getSpin());
        Transform finTransform = new Transform(finalEpoch, finAttitude.getRotation());
        System.out.println("fin spin in EME2000 = " + finTransform.transformVector(finAttitude.getSpin()));

What I am doing here is to set an attitude providing a spin rate. Then I use it to define a fixed rate attitude provider and finally I propagate.
The results in this case are the following:

first rot angle = -0.0
second rot angle = 0.0
third rot angle = -0.0
init spin in sat = {0.1745329252; 0.3490658504; 0.5235987756}
init spin in EME2000 = {0.1745329252; 0.3490658504; 0.5235987756}
----------
first rot angle = -0.09152175482841884
second rot angle = -0.13977086485070148
third rot angle = -0.2313333923783455
fin spin in sat = {0.1745329252; 0.3490658504; 0.5235987756}
fin spin in EME2000 = {0.1745329252; 0.3490658504; 0.5235987756}

Then I tried to change the definition of the initial attitude providing an initial rotation between the attitude and the reference frame, as reported:

        Attitude initAttitude = new Attitude(startingEpoch,
                                             FramesFactory.getEME2000(),
                                             new Rotation(RotationOrder.XYZ, RotationConvention.FRAME_TRANSFORM, 0.1, 0.2, 0.3),
                                             new Vector3D(10 * Math.PI / 180, 20 * Math.PI / 180, 30 * Math.PI / 180),
                                             new Vector3D(0, 0, 0));

Running again the code, I get:

first rot angle = 0.09999999999999998
second rot angle = 0.20000000000000004
third rot angle = 0.3
init spin in sat = {0.1745329252; 0.3490658504; 0.5235987756}
init spin in EME2000 = {0.189235889; 0.3597387985; 0.5111185409}
----------
first rot angle = 0.054659698819354584
second rot angle = 0.03926550710026943
third rot angle = 0.08050722697220719
fin spin in sat = {0.1745329252; 0.3490658504; 0.5235987756}
fin spin in EME2000 = {0.1844533141; 0.3634935849; 0.5102095416}

I cannot understand why the “fin spin in EME2000” is now different from the “init spin in EME2000”. Why does the definition of an initial attitude different from the reference one imply these two results?
I feel like I am missing something very stupid in all this.

Thank you!
Thomas


UPDATE:
I think I just found the problem… The last line of the code should be:

System.out.println("fin spin in EME2000 = " + finTransform.getInverse().transformVector(finAttitude.getSpin()));

Right?

Yes, this is right.
The Transform.transformVector method semantics correspond to a vector operator, i.e.something that takes a vector 1 expressed by its coordinates in frame 1 and change it to vector 2 expressed by its coordinates in frame 2. The physical relationship between vector 1 and vector 2 as well as the relationship between frame 1 and frame 2 depend on what the transform instance represents. In your case, the transform is built from a rotation manually extracted from an attitude. In this case, the physical vectors vector 1 and vector 2 are the same (here they will both represent the same thing: the instantaneous rotation rate) and the frames will be different. Frame 1 will be an inertial frame and frame 2 will be the spacecraft frame. As the getSpin method in Attitude returns the spin in the spacecraft frame, you have to invert the transform if you want to go from there to the inertial frame.

Another point that may be of interest in this case.
In the code above, a Transform is built directly from a Rotation, that itself is extracted from an Attitude, which is extracted from a SpacecraftState. This can be simplified by calling finSpacecraftState.toTransform(), which would combine both the position of the spacecraft as a translation (including derivatives) and the attitude (including derivatives).
This however implies a few objects are built under the hood, so it makes the code simpler and more readable, but may waste some time building complex objects if only a small part of them is used (calling transformVector, which ignores both the translation and all derivatives). So if this part is used millions of time during a propagation, it may be worth avoiding all Transform objects and just use the Rotation API, with something like finAttitude.getRotation().applyInverseTo(finAttitude.getSpin()).