1   /* Copyright 2002-2026 Bryan Cazabonne
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    * Bryan Cazabonne 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.propagation.analytical.tle.generation;
18  
19  import org.hipparchus.CalculusFieldElement;
20  import org.hipparchus.analysis.differentiation.Gradient;
21  import org.hipparchus.analysis.differentiation.GradientField;
22  import org.hipparchus.linear.MatrixUtils;
23  import org.hipparchus.linear.RealMatrix;
24  import org.hipparchus.util.FastMath;
25  import org.hipparchus.util.MathUtils;
26  import org.orekit.frames.Frame;
27  import org.orekit.orbits.AbstractOrbitalParameterFactory;
28  import org.orekit.orbits.FieldKeplerianOrbit;
29  import org.orekit.orbits.KeplerianOrbit;
30  import org.orekit.orbits.Orbit;
31  import org.orekit.orbits.OrbitType;
32  import org.orekit.orbits.PositionAngleType;
33  import org.orekit.propagation.FieldSpacecraftState;
34  import org.orekit.propagation.SpacecraftState;
35  import org.orekit.propagation.analytical.tle.FieldTLE;
36  import org.orekit.propagation.analytical.tle.FieldTLEPropagator;
37  import org.orekit.propagation.analytical.tle.TLE;
38  import org.orekit.propagation.analytical.tle.TLEConstants;
39  import org.orekit.propagation.conversion.osc2mean.OsculatingToMeanConverter;
40  import org.orekit.time.FieldAbsoluteDate;
41  import org.orekit.utils.ParameterDriver;
42  import org.orekit.utils.ParameterDriversList;
43  import org.orekit.utils.ParameterDriversList.DelegatingDriver;
44  import org.orekit.utils.TimeStampedFieldPVCoordinates;
45  
46  import java.util.List;
47  
48  /**
49   * Base class for generating a TLE.
50   * @author Bryan Cazabonne
51   * @since 12.0
52   */
53  public abstract class TleGenerationAlgorithm extends AbstractOrbitalParameterFactory<TLE> {
54  
55      /** Name for mean motion. */
56      public static final String MEAN_MOTION = "TleMeanMotion";
57  
58      /** Name for eccentricity. */
59      public static final String ECCENTRICITY   = "TleEccentricity";
60  
61      /** Name for inclination. */
62      public static final String INCLINATION   = "TleInclination";
63  
64      /** Name for periapsis argument. */
65      public static final String PERIAPSIS_ARGUMENT = "TlePeriapsisArgument";
66  
67      /** Name for right ascension of ascending node. */
68      public static final String RAAN    = "TleRighAscensionAscendingNode";
69  
70      /** Name for mean anomaly. */
71      public static final String MEAN_ANOM = "TleMeanAnomaly";
72  
73      /** Parameter name for B* coefficient. */
74      public static final String B_STAR = "BSTAR";
75  
76      /** B* scaling factor.
77       * <p>
78       * We use a power of 2 to avoid numeric noise introduction
79       * in the multiplications/divisions sequences.
80       * </p>
81       */
82      public static final double B_STAR_SCALE = FastMath.scalb(1.0, -20);
83  
84      /** Number of orbital parameters, i.e. of both rows and columns of the Jacobians. */
85      private static final int DEFAULT_STATE_DIMENSION = 6;
86  
87      /** Template TLE. */
88      private final TLE templateTLE;
89  
90      /** Non-Keplerian drivers (containing only for ballistic coefficient parameter). */
91      private ParameterDriversList nonKeplerianDrivers;
92  
93      /** Osculating to mean orbit converter. */
94      private final OsculatingToMeanConverter converter;
95  
96      /** Default constructor.
97       * @param templateTLE template TLE
98       * @param teme teme frame
99       * @param converter osculating to mean orbit converter
100      */
101     protected TleGenerationAlgorithm(final TLE templateTLE,  final Frame teme,
102                                      final OsculatingToMeanConverter converter) {
103         super(null, createOrbitalParametersDrivers(templateTLE), teme, PositionAngleType.MEAN,
104               templateTLE.getDate(), TLEConstants.MU);
105         this.templateTLE = templateTLE;
106 
107         // create model parameter drivers
108         nonKeplerianDrivers = new ParameterDriversList();
109         nonKeplerianDrivers.add(new ParameterDriver(B_STAR, templateTLE.getBStar(), B_STAR_SCALE,
110                                                     Double.NEGATIVE_INFINITY,
111                                                     Double.POSITIVE_INFINITY));
112 
113         // conversion algorithm
114         this.converter = converter;
115 
116     }
117 
118     /** Get the template TLE.
119      * @return template TLE
120      */
121     public TLE getTemplateTLE() {
122         return templateTLE;
123     }
124 
125     /** Get the osculating to mean orbit converter.
126      * @return osculating to mean orbit converter
127      * @since 14.0
128      */
129     public OsculatingToMeanConverter getConverter() {
130         return converter;
131     }
132 
133     /** {@inheritDoc} */
134     @Override
135     public ParameterDriversList getNonKeplerianParametersDrivers() {
136         return nonKeplerianDrivers;
137     }
138 
139     /** Create orbital parameter drivers.
140      * @param tle reference TLE
141      * @return drivers
142      */
143     private static ParameterDriversList createOrbitalParametersDrivers(final TLE tle) {
144         final ParameterDriversList drivers = new ParameterDriversList();
145         drivers.add(new ParameterDriver(MEAN_MOTION, tle.getMeanMotion(),
146                                         FastMath.scalb(1.0, -32),
147                                         0, Double.POSITIVE_INFINITY));
148         drivers.add(new ParameterDriver(ECCENTRICITY, tle.getE(),
149                                         FastMath.scalb(1.0, -22),
150                                         0.0, 1.0));
151         drivers.add(new ParameterDriver(INCLINATION, tle.getI(),
152                                         FastMath.scalb(1.0, -22),
153                                         0, FastMath.PI));
154         drivers.add(new ParameterDriver(PERIAPSIS_ARGUMENT, tle.getPeriapsisArgument(),
155                                         FastMath.scalb(1.0, -22),
156                                         Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY));
157         drivers.add(new ParameterDriver(RAAN, tle.getRaan(),
158                                         FastMath.scalb(1.0, -22),
159                                         Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY));
160         drivers.add(new ParameterDriver(MEAN_ANOM, tle.getMeanAnomaly(),
161                                         FastMath.scalb(1.0, -22),
162                                         Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY));
163         return drivers;
164     }
165 
166     /** {@inheritDoc} */
167     @Override
168     protected double[] toArray(final Orbit orbit) {
169 
170         // fix both frame and type
171         final Orbit mean               = converter.convertToMean(orbit);
172         final Orbit partiallyConverted = orbit.getFrame() == getFrame() ? mean : mean.inFrame(getFrame());
173         final Orbit fullyConverted     = OrbitType.KEPLERIAN.convertType(partiallyConverted);
174 
175         // retrieve orbital parameters
176         final double[] stateVector = new double[6];
177         OrbitType.KEPLERIAN.mapOrbitToArray(fullyConverted, PositionAngleType.MEAN, stateVector, null);
178 
179         // TLE uses mean motion as first parameter, not semi major axis as Keplerian orbit
180         stateVector[0] = fullyConverted.getKeplerianMeanMotion();
181 
182         return stateVector;
183 
184     }
185 
186     /** {@inheritDoc} */
187     @Override
188     public TLE createFromDrivers() {
189 
190         final List<DelegatingDriver> drivers = getOrbitalParametersDrivers().getDrivers();
191 
192         // adjust revolution number
193         // as neither SDP4 nor SGP4 use mean motion derivatives, we intentionally ignore them here
194         final double latArg0 =
195             MathUtils.normalizeAngle(drivers.get(3).getValue() + drivers.get(5).getValue(),
196                                      FastMath.PI);
197         final double deltaT   = getDate().durationFrom(templateTLE.getDate());
198         final double latArg1  = latArg0 + deltaT * drivers.get(0).getValue();
199         final int    deltaRev = (int) FastMath.floor(latArg1 / MathUtils.TWO_PI);
200 
201         return new TLE(templateTLE.getSatelliteNumber(), templateTLE.getClassification(),
202                        templateTLE.getLaunchYear(), templateTLE.getLaunchNumber(), templateTLE.getLaunchPiece(),
203                        templateTLE.getEphemerisType(),
204                        templateTLE.getElementNumber() + 1,
205                        getDate(),
206                        drivers.get(0).getValue(),
207                        templateTLE.getMeanMotionFirstDerivative(), templateTLE.getMeanMotionSecondDerivative(),
208                        drivers.get(1).getValue(),
209                        drivers.get(2).getValue(),
210                        drivers.get(3).getValue(),
211                        drivers.get(4).getValue(),
212                        drivers.get(5).getValue(),
213                        templateTLE.getRevolutionNumberAtEpoch() + deltaRev,
214                        getBStar(),
215                        templateTLE.getUtc());
216     }
217 
218     /** {@inheritDoc}
219      * <p>
220      * The TLE orbital elements are related to the Cartesian coordinates by the SGP4/SDP4
221      * model itself, which has no closed-form derivatives, so the Jacobian is obtained by
222      * automatic differentiation: the six elements of the TLE built from the current drivers
223      * are turned into {@link Gradient} variables, and the resulting TLE is evaluated at its
224      * own epoch.
225      * </p>
226      */
227     @Override
228     public RealMatrix getJacobianWrtParameters() {
229         return getJacobianWrtParameters(createFromDrivers());
230     }
231 
232     /** Get the Jacobian of the Cartesian coordinates with respect to the orbital elements of a TLE.
233      * <p>
234      * The TLE orbital elements are related to the Cartesian coordinates by the SGP4/SDP4 model
235      * itself, which has no closed-form derivatives, so the Jacobian is obtained by automatic
236      * differentiation: the six orbital elements are turned into {@link Gradient} variables and
237      * the TLE is evaluated at its own epoch. All the remaining data (identification, mean motion
238      * derivatives, B*) are held constant.
239      * </p>
240      * @param tle TLE holding the orbital elements the Jacobian is computed with respect to
241      * @return jacobian matrix dC/dB, at the TLE epoch and in the TEME frame
242      */
243     public RealMatrix getJacobianWrtParameters(final TLE tle) {
244 
245         // evaluate the Cartesian coordinates from a TLE whose orbital elements are variables
246         final TimeStampedFieldPVCoordinates<Gradient> pv =
247             FieldTLEPropagator.selectExtrapolator(toGradient(tle), getFrame()).
248             getBaseInitialState().
249             getPVCoordinates();
250 
251         // gather the derivatives of each Cartesian coordinate into a row
252         final RealMatrix jacobian = MatrixUtils.createRealMatrix(DEFAULT_STATE_DIMENSION, DEFAULT_STATE_DIMENSION);
253         jacobian.setRow(0, pv.getPosition().getX().getGradient());
254         jacobian.setRow(1, pv.getPosition().getY().getGradient());
255         jacobian.setRow(2, pv.getPosition().getZ().getGradient());
256         jacobian.setRow(3, pv.getVelocity().getX().getGradient());
257         jacobian.setRow(4, pv.getVelocity().getY().getGradient());
258         jacobian.setRow(5, pv.getVelocity().getZ().getGradient());
259 
260         return jacobian;
261 
262     }
263 
264     /** Convert a TLE into one whose orbital elements are gradient variables.
265      * @param tle TLE to convert
266      * @return converted TLE, whose orbital elements carry their own derivatives
267      */
268     // FIXME: should this one be in TLE class instead ?
269     private static FieldTLE<Gradient> toGradient(final TLE tle) {
270         final GradientField field = GradientField.getField(DEFAULT_STATE_DIMENSION);
271         return new FieldTLE<>(tle.getSatelliteNumber(), tle.getClassification(),
272                               tle.getLaunchYear(), tle.getLaunchNumber(), tle.getLaunchPiece(),
273                               tle.getEphemerisType(), tle.getElementNumber(),
274                               new FieldAbsoluteDate<>(field, tle.getDate()),
275                               Gradient.variable(DEFAULT_STATE_DIMENSION, 0, tle.getMeanMotion()),
276                               Gradient.constant(DEFAULT_STATE_DIMENSION, tle.getMeanMotionFirstDerivative()),
277                               Gradient.constant(DEFAULT_STATE_DIMENSION, tle.getMeanMotionSecondDerivative()),
278                               Gradient.variable(DEFAULT_STATE_DIMENSION, 1, tle.getE()),
279                               Gradient.variable(DEFAULT_STATE_DIMENSION, 2, tle.getI()),
280                               Gradient.variable(DEFAULT_STATE_DIMENSION, 3, tle.getPeriapsisArgument()),
281                               Gradient.variable(DEFAULT_STATE_DIMENSION, 4, tle.getRaan()),
282                               Gradient.variable(DEFAULT_STATE_DIMENSION, 5, tle.getMeanAnomaly()),
283                               tle.getRevolutionNumberAtEpoch(),
284                               Gradient.constant(DEFAULT_STATE_DIMENSION, tle.getBStar()),
285                               tle.getUtc());
286     }
287 
288     /** Get the current B-star value.
289      * @return current B-star value
290      */
291     protected double getBStar() {
292         return nonKeplerianDrivers.getDrivers().getFirst().getValue();
293     }
294 
295     /**
296      * Generate a TLE from a given spacecraft state and a template TLE.
297      * <p>
298      * The template TLE is only used to get identifiers like satellite
299      * number, launch year, etc.
300      * In other words, the keplerian elements contained in the generated
301      * TLE are based on the provided state and not the template TLE.
302      * </p>
303      * @param state spacecraft state
304      * @param newTemplateTLE template TLE
305      * @return a TLE corresponding to the given state
306      */
307     public TLE generate(final SpacecraftState state, final TLE newTemplateTLE) {
308         final KeplerianOrbit mean =
309             (KeplerianOrbit) OrbitType.KEPLERIAN.convertType(converter.convertToMean(state.getOrbit()));
310         return TleGenerationUtil.newTLE(mean, newTemplateTLE);
311     }
312 
313     /**
314      * Generate a TLE from a given spacecraft state and a template TLE.
315      * <p>
316      * The template TLE is only used to get identifiers like satellite
317      * number, launch year, etc.
318      * In other words, the keplerian elements contained in the generated
319      * TLE are based on the provided state and not the template TLE.
320      * </p>
321      * @param <T> type of the elements
322      * @param state spacecraft state
323      * @param newTemplateTLE template TLE
324      * @return a TLE corresponding to the given state
325      */
326     public <T extends CalculusFieldElement<T>> FieldTLE<T> generate(final FieldSpacecraftState<T> state,
327                                                                     final FieldTLE<T> newTemplateTLE) {
328         final FieldKeplerianOrbit<T> mean =
329             (FieldKeplerianOrbit<T>) OrbitType.KEPLERIAN.convertType(converter.convertToMean(state.getOrbit()));
330         return TleGenerationUtil.newTLE(mean, newTemplateTLE);
331     }
332 
333     /** {@inheritDoc} */
334     @Override
335     public TleGenerationAlgorithm clone() {
336 
337         final TleGenerationAlgorithm clone = (TleGenerationAlgorithm) super.clone();
338 
339         // de-couple b-star driver
340         final ParameterDriversList newDrivers = new ParameterDriversList();
341         final ParameterDriver driver = nonKeplerianDrivers.getDrivers().getFirst();
342         newDrivers.add(new ParameterDriver(driver.getName(), driver.getValue(), driver.getScale(),
343                                            driver.getMinValue(), driver.getMaxValue()));
344         clone.nonKeplerianDrivers = newDrivers;
345 
346         return clone;
347 
348     }
349 
350 }