1   /* Copyright 2022-2024 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 Vector3D positionInJ2Frame = state.getPosition(frame);
174         final double squaredRadius = positionInJ2Frame.getNormSq();
175         final double squaredZ = positionInJ2Frame.getZ() * positionInJ2Frame.getZ();
176         final double ratioTimesFive = 5. * squaredZ / squaredRadius;
177         final double ratioTimesFiveMinusOne = ratioTimesFive - 1.;
178         final double accelerationX = positionInJ2Frame.getX() * ratioTimesFiveMinusOne;
179         final double accelerationY = positionInJ2Frame.getY() * ratioTimesFiveMinusOne;
180         final double accelerationZ = positionInJ2Frame.getZ() * (ratioTimesFive - 3);
181         final Vector3D accelerationInJ2Frame = new Vector3D(accelerationX, accelerationY, accelerationZ);
182         final AbsoluteDate date = state.getDate();
183         final StaticTransform fromJ2FrameToPropagationOne = frame.getStaticTransformTo(state.getFrame(), date);
184         final Vector3D transformedAcceleration = fromJ2FrameToPropagationOne.transformVector(accelerationInJ2Frame);
185         final double j2 = j2OverTime.value(date);
186         final double squaredRadiiRatio = rEq * rEq / squaredRadius;
187         final double cubedRadius = squaredRadius * FastMath.sqrt(squaredRadius);
188         final double factor = 3 * j2 * mu * squaredRadiiRatio / (2 * cubedRadius);
189         return transformedAcceleration.scalarMultiply(factor);
190     }
191 
192     /** {@inheritDoc} */
193     @Override
194     public <T extends CalculusFieldElement<T>> FieldVector3D<T> acceleration(final FieldSpacecraftState<T> state,
195                                                                              final T[] parameters) {
196         final FieldVector3D<T> positionInJ2Frame = state.getPosition(frame);
197         final T squaredRadius = positionInJ2Frame.getNormSq();
198         final T squaredZ = positionInJ2Frame.getZ().square();
199         final T ratioTimesFive = squaredZ.multiply(5.).divide(squaredRadius);
200         final T ratioTimesFiveMinusOne = ratioTimesFive.subtract(1.);
201         final T accelerationX = positionInJ2Frame.getX().multiply(ratioTimesFiveMinusOne);
202         final T accelerationY = positionInJ2Frame.getY().multiply(ratioTimesFiveMinusOne);
203         final T accelerationZ = positionInJ2Frame.getZ().multiply(ratioTimesFive.subtract(3.));
204         final FieldVector3D<T> accelerationInJ2Frame = new FieldVector3D<>(accelerationX, accelerationY, accelerationZ);
205         final FieldAbsoluteDate<T> date = state.getDate();
206         final FieldStaticTransform<T> fromJ2FrameToPropagationOne = frame.getStaticTransformTo(state.getFrame(), date);
207         final FieldVector3D<T> transformedAcceleration = fromJ2FrameToPropagationOne.transformVector(accelerationInJ2Frame);
208         final T j2 = j2OverTime.value(date);
209         final T squaredRadiiRatio = squaredRadius.reciprocal().multiply(rEq * rEq);
210         final T cubedRadius = squaredRadius.multiply(FastMath.sqrt(squaredRadius));
211         final T factor = j2.multiply(mu).multiply(3.).multiply(squaredRadiiRatio).divide(cubedRadius.multiply(2));
212         return transformedAcceleration.scalarMultiply(factor);
213     }
214 
215     /** {@inheritDoc} */
216     @Override
217     public List<ParameterDriver> getParametersDrivers() {
218         return Collections.emptyList();
219     }
220 }