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