1   /* Copyright 2022-2026 Romain Serra
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.forces.gravity;
18  
19  import org.hipparchus.CalculusFieldElement;
20  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
21  import org.hipparchus.geometry.euclidean.threed.Vector3D;
22  import org.hipparchus.util.FastMath;
23  import org.orekit.forces.ForceModel;
24  import org.orekit.forces.gravity.potential.UnnormalizedSphericalHarmonicsProvider;
25  import org.orekit.frames.FieldStaticTransform;
26  import org.orekit.frames.Frame;
27  import org.orekit.frames.StaticTransform;
28  import org.orekit.propagation.FieldSpacecraftState;
29  import org.orekit.propagation.SpacecraftState;
30  import org.orekit.time.AbsoluteDate;
31  import org.orekit.time.FieldAbsoluteDate;
32  import org.orekit.time.TimeScalarFunction;
33  
34  /** J2-only force model.
35   * This class models the oblateness part alone of the central body's potential (degree 2 and order 0),
36   * whilst avoiding the computational overhead of generic NxM spherical harmonics.
37   *
38   * <p>
39   * This J2 coefficient has same magnitude and opposite sign than the so-called unnormalized C20 coefficient.
40   * </p>
41   *
42   * <p>
43   * This class should not be used in combination of {@link HolmesFeatherstoneAttractionModel},
44   * otherwise the J2 term would be taken into account twice.
45   * </p>
46   *
47   * @author Romain Serra
48   */
49  public class J2OnlyPerturbation implements ForceModel {
50  
51      /** Central body's gravitational constant. */
52      private final double mu;
53  
54      /** Central body's equatorial radius. */
55      private final double rEq;
56  
57      /** Central body's J2 coefficient as a function of time. */
58      private final TimeScalarFunction j2OverTime;
59  
60      /** Frame where J2 applies. */
61      private final Frame frame;
62  
63      /** Constructor with {@link TimeScalarFunction}.
64       * It is the user's responsibility to make sure the Field and double versions are consistent with each other.
65       * @param mu central body's gravitational constant
66       * @param rEq central body's equatorial radius
67       * @param j2OverTime J2 coefficient as a function of time.
68       * @param frame frame where J2 applies
69       */
70      public J2OnlyPerturbation(final double mu, final double rEq, final TimeScalarFunction j2OverTime,
71                                final Frame frame) {
72          this.mu = mu;
73          this.rEq = rEq;
74          this.j2OverTime = j2OverTime;
75          this.frame = frame;
76      }
77  
78      /** Constructor with constant J2.
79       * @param mu central body gravitational constant
80       * @param rEq central body's equatorial radius
81       * @param constantJ2 constant J2 coefficient
82       * @param frame frame where J2 applies
83       */
84      public J2OnlyPerturbation(final double mu, final double rEq, final double constantJ2, final Frame frame) {
85          this.mu = mu;
86          this.rEq = rEq;
87          this.frame = frame;
88          this.j2OverTime = new TimeScalarFunction() {
89              @Override
90              public double value(final AbsoluteDate date) {
91                  return constantJ2;
92              }
93  
94              @Override
95              public <T extends CalculusFieldElement<T>> T value(final FieldAbsoluteDate<T> date) {
96                  return date.getField().getZero().newInstance(constantJ2);
97              }
98          };
99      }
100 
101     /** Constructor with spherical harmonics provider.
102      * @param harmonicsProvider spherical harmonics provider of unnormalized coefficients
103      * @param frame frame where J2 applies
104      */
105     public J2OnlyPerturbation(final UnnormalizedSphericalHarmonicsProvider harmonicsProvider, final Frame frame) {
106         this.mu = harmonicsProvider.getMu();
107         this.rEq = harmonicsProvider.getAe();
108         this.frame = frame;
109         this.j2OverTime = new TimeScalarFunction() {
110             @Override
111             public double value(final AbsoluteDate date) {
112                 return -harmonicsProvider.getUnnormalizedC20(date);
113             }
114 
115             @Override
116             public <T extends CalculusFieldElement<T>> T value(final FieldAbsoluteDate<T> date) {
117                 return date.getField().getZero().newInstance(value(date.toAbsoluteDate()));
118             }
119         };
120     }
121 
122     /** Getter for mu.
123      * @return mu
124      */
125     public double getMu() {
126         return mu;
127     }
128 
129     /** Getter for equatorial radius.
130      * @return equatorial radius
131      */
132     public double getrEq() {
133         return rEq;
134     }
135 
136     /** Getter for frame.
137      * @return frame
138      */
139     public Frame getFrame() {
140         return frame;
141     }
142 
143     /** Return J2 at requested date.
144      * @param date epoch at which J2 coefficient should be retrieved
145      * @return J2 coefficient
146      */
147     public double getJ2(final AbsoluteDate date) {
148         return j2OverTime.value(date);
149     }
150 
151     /** Return J2 at requested date (Field version).
152      * @param <T> field
153      * @param date epoch at which J2 coefficient should be retrieved
154      * @return J2 coefficient
155      */
156     public <T extends CalculusFieldElement<T>> T getJ2(final FieldAbsoluteDate<T> date) {
157         return j2OverTime.value(date);
158     }
159 
160     /** {@inheritDoc} */
161     @Override
162     public boolean dependsOnPositionOnly() {
163         return true;
164     }
165 
166     /** {@inheritDoc} */
167     @Override
168     public Vector3D acceleration(final SpacecraftState state, final double[] parameters) {
169         final AbsoluteDate date = state.getDate();
170         final StaticTransform fromPropagationToJ2Frame = state.getFrame().getStaticTransformTo(frame, date);
171         final Vector3D positionInJ2Frame = fromPropagationToJ2Frame.transformPosition(state.getPosition());
172         final double j2 = j2OverTime.value(date);
173         final Vector3D accelerationInJ2Frame = computeAccelerationInJ2Frame(positionInJ2Frame, mu, rEq, j2);
174         final StaticTransform fromJ2FrameToPropagationOne = fromPropagationToJ2Frame.getStaticInverse();
175         return fromJ2FrameToPropagationOne.transformVector(accelerationInJ2Frame);
176     }
177 
178     /**
179      * Compute acceleration in J2 frame.
180      * @param positionInJ2Frame position in J2 frame@
181      * @param mu gravitational parameter
182      * @param rEq equatorial radius
183      * @param j2 J2 coefficient
184      * @return acceleration in J2 frame
185      */
186     public static Vector3D computeAccelerationInJ2Frame(final Vector3D positionInJ2Frame, final double mu,
187                                                         final double rEq, final double j2) {
188         final double squaredRadius = positionInJ2Frame.getNorm2Sq();
189         final double squaredZ = positionInJ2Frame.getZ() * positionInJ2Frame.getZ();
190         final double ratioTimesFive = 5. * squaredZ / squaredRadius;
191         final double ratioTimesFiveMinusOne = ratioTimesFive - 1.;
192         final double componentX = positionInJ2Frame.getX() * ratioTimesFiveMinusOne;
193         final double componentY = positionInJ2Frame.getY() * ratioTimesFiveMinusOne;
194         final double componentZ = positionInJ2Frame.getZ() * (ratioTimesFive - 3);
195         final double squaredRadiiRatio = rEq * rEq / squaredRadius;
196         final double cubedRadius = squaredRadius * FastMath.sqrt(squaredRadius);
197         final double factor = 3 * j2 * mu * squaredRadiiRatio / (2 * cubedRadius);
198         return new Vector3D(componentX, componentY, componentZ).scalarMultiply(factor);
199     }
200 
201     /** {@inheritDoc} */
202     @Override
203     public <T extends CalculusFieldElement<T>> FieldVector3D<T> acceleration(final FieldSpacecraftState<T> state,
204                                                                              final T[] parameters) {
205         final FieldAbsoluteDate<T> date = state.getDate();
206         final FieldStaticTransform<T> fromPropagationToJ2Frame = state.getFrame().getStaticTransformTo(frame, date);
207         final FieldVector3D<T> positionInJ2Frame = fromPropagationToJ2Frame.transformPosition(state.getPosition());
208         final FieldVector3D<T> accelerationInJ2Frame = computeAccelerationInJ2Frame(positionInJ2Frame, mu, rEq,
209                 j2OverTime.value(date));
210         final FieldStaticTransform<T> fromJ2FrameToPropagation = fromPropagationToJ2Frame.getStaticInverse();
211         return fromJ2FrameToPropagation.transformVector(accelerationInJ2Frame);
212     }
213 
214     /**
215      * Compute acceleration in J2 frame. Field version.
216      * @param positionInJ2Frame position in J2 frame@
217      * @param mu gravitational parameter
218      * @param rEq equatorial radius
219      * @param j2 J2 coefficient
220      * @param <T> field type
221      * @return acceleration in J2 frame
222      */
223     public static <T extends CalculusFieldElement<T>> FieldVector3D<T> computeAccelerationInJ2Frame(final FieldVector3D<T> positionInJ2Frame,
224                                                                                                     final double mu, final double rEq, final T j2) {
225         final T squaredRadius = positionInJ2Frame.getNorm2Sq();
226         final T squaredZ = positionInJ2Frame.getZ().square();
227         final T ratioTimesFive = squaredZ.multiply(5.).divide(squaredRadius);
228         final T ratioTimesFiveMinusOne = ratioTimesFive.subtract(1.);
229         final T componentX = positionInJ2Frame.getX().multiply(ratioTimesFiveMinusOne);
230         final T componentY = positionInJ2Frame.getY().multiply(ratioTimesFiveMinusOne);
231         final T componentZ = positionInJ2Frame.getZ().multiply(ratioTimesFive.subtract(3.));
232         final T squaredRadiiRatio = squaredRadius.reciprocal().multiply(rEq * rEq);
233         final T cubedRadius = squaredRadius.multiply(FastMath.sqrt(squaredRadius));
234         final T factor = j2.multiply(mu).multiply(3.).multiply(squaredRadiiRatio).divide(cubedRadius.multiply(2));
235         return new FieldVector3D<>(componentX, componentY, componentZ).scalarMultiply(factor);
236     }
237 
238 }