LeastSquaresTleGenerationAlgorithm.java
/* Copyright 2002-2024 Mark Rutten
* Licensed to CS GROUP (CS) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* Mark Rutten licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.orekit.propagation.analytical.tle.generation;
import java.util.function.UnaryOperator;
import org.hipparchus.CalculusFieldElement;
import org.hipparchus.Field;
import org.hipparchus.analysis.differentiation.Gradient;
import org.hipparchus.analysis.differentiation.GradientField;
import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.hipparchus.linear.DiagonalMatrix;
import org.hipparchus.linear.FieldVector;
import org.hipparchus.linear.MatrixUtils;
import org.hipparchus.linear.RealMatrix;
import org.hipparchus.linear.RealVector;
import org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresBuilder;
import org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresOptimizer;
import org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresProblem;
import org.hipparchus.optim.nonlinear.vector.leastsquares.LevenbergMarquardtOptimizer;
import org.hipparchus.optim.nonlinear.vector.leastsquares.MultivariateJacobianFunction;
import org.hipparchus.util.Pair;
import org.orekit.annotation.DefaultDataContext;
import org.orekit.data.DataContext;
import org.orekit.frames.Frame;
import org.orekit.orbits.FieldKeplerianOrbit;
import org.orekit.orbits.KeplerianOrbit;
import org.orekit.propagation.FieldSpacecraftState;
import org.orekit.propagation.SpacecraftState;
import org.orekit.propagation.analytical.tle.FieldTLE;
import org.orekit.propagation.analytical.tle.FieldTLEPropagator;
import org.orekit.propagation.analytical.tle.TLE;
import org.orekit.propagation.analytical.tle.TLEPropagator;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.FieldAbsoluteDate;
import org.orekit.time.TimeScale;
import org.orekit.utils.FieldPVCoordinates;
import org.orekit.utils.PVCoordinates;
import org.orekit.utils.ParameterDriver;
/**
* Least squares method to generate a usable TLE from a spacecraft state.
*
* @author Mark Rutten
* @since 12.0
*/
public class LeastSquaresTleGenerationAlgorithm implements TleGenerationAlgorithm {
/** Default value for maximum number of iterations.*/
public static final int DEFAULT_MAX_ITERATIONS = 1000;
/** UTC scale. */
private final TimeScale utc;
/** TEME frame. */
private final Frame teme;
/** Maximum number of iterations. */
private final int maxIterations;
/** RMS. */
private double rms;
/**
* Default constructor.
* <p>
* Uses the {@link DataContext#getDefault() default data context} as well as
* {@link #DEFAULT_MAX_ITERATIONS}.
* </p>
*/
@DefaultDataContext
public LeastSquaresTleGenerationAlgorithm() {
this(DEFAULT_MAX_ITERATIONS);
}
/**
* Default constructor.
* <p>
* Uses the {@link DataContext#getDefault() default data context}.
* </p>
* @param maxIterations maximum number of iterations for convergence
*/
@DefaultDataContext
public LeastSquaresTleGenerationAlgorithm(final int maxIterations) {
this(maxIterations, DataContext.getDefault().getTimeScales().getUTC(),
DataContext.getDefault().getFrames().getTEME());
}
/**
* Constructor.
* @param maxIterations maximum number of iterations for convergence
* @param utc UTC time scale
* @param teme TEME frame
*/
public LeastSquaresTleGenerationAlgorithm(final int maxIterations,
final TimeScale utc, final Frame teme) {
this.maxIterations = maxIterations;
this.utc = utc;
this.teme = teme;
this.rms = Double.MAX_VALUE;
}
/** {@inheritDoc} */
@Override
public TLE generate(final SpacecraftState state, final TLE templateTLE) {
// Generation epoch
final AbsoluteDate epoch = state.getDate();
// State vector
final RealVector stateVector = MatrixUtils.createRealVector(6);
// Position/Velocity
final Vector3D position = state.getPVCoordinates().getPosition();
final Vector3D velocity = state.getPVCoordinates().getVelocity();
// Fill state vector
stateVector.setEntry(0, position.getX());
stateVector.setEntry(1, position.getY());
stateVector.setEntry(2, position.getZ());
stateVector.setEntry(3, velocity.getX());
stateVector.setEntry(4, velocity.getY());
stateVector.setEntry(5, velocity.getZ());
// Create the initial guess of the least squares problem
final RealVector startState = MatrixUtils.createRealVector(7);
startState.setSubVector(0, stateVector.getSubVector(0, 6));
// Weights
final double[] weights = new double[6];
final double velocityWeight = state.getPVCoordinates().getVelocity().getNorm() * state.getPosition().getNormSq() / state.getMu();
for (int i = 0; i < 3; i++) {
weights[i] = 1.0;
weights[i + 3] = velocityWeight;
}
// Time difference between template TLE and spacecraft state
final double dt = state.getDate().durationFrom(templateTLE.getDate());
// Construct least squares problem
final LeastSquaresProblem problem =
new LeastSquaresBuilder().maxIterations(maxIterations)
.maxEvaluations(maxIterations)
.model(new ObjectiveFunction(templateTLE, dt))
.target(stateVector)
.weight(new DiagonalMatrix(weights))
.start(startState)
.build();
// Solve least squares
final LevenbergMarquardtOptimizer optimizer = new LevenbergMarquardtOptimizer();
final LeastSquaresOptimizer.Optimum optimum = optimizer.optimize(problem);
rms = optimum.getRMS();
// Create new TLE from mean state
final Vector3D positionEstimated = new Vector3D(optimum.getPoint().getSubVector(0, 3).toArray());
final Vector3D velocityEstimated = new Vector3D(optimum.getPoint().getSubVector(3, 3).toArray());
final PVCoordinates pvCoordinates = new PVCoordinates(positionEstimated, velocityEstimated);
final KeplerianOrbit orbit = new KeplerianOrbit(pvCoordinates, teme, epoch, state.getMu());
final TLE generated = TleGenerationUtil.newTLE(orbit, templateTLE, templateTLE.getBStar(), utc);
// Verify if parameters are estimated
for (final ParameterDriver templateDrivers : templateTLE.getParametersDrivers()) {
if (templateDrivers.isSelected()) {
// Set to selected for the new TLE
generated.getParameterDriver(templateDrivers.getName()).setSelected(true);
}
}
// Return
return generated;
}
/**
* Get the Root Mean Square of the TLE estimation.
* <p>
* Be careful that the RMS is updated each time the
* {@link LeastSquaresTleGenerationAlgorithm#generate(SpacecraftState, TLE)}
* method is called.
* </p>
* @return the RMS
*/
public double getRms() {
return rms;
}
/** {@inheritDoc} */
@Override
public <T extends CalculusFieldElement<T>> FieldTLE<T> generate(final FieldSpacecraftState<T> state,
final FieldTLE<T> templateTLE) {
throw new UnsupportedOperationException();
}
/** Least squares model. */
private class ObjectiveFunction implements MultivariateJacobianFunction {
/** Template TLE. */
private final FieldTLE<Gradient> templateTLE;
/** Time difference between template TLE and spacecraft state (s). */
private final double dt;
/**
* Constructor.
* @param templateTLE template TLE
* @param dt time difference between template TLE and spacecraft state (s)
*/
ObjectiveFunction(final TLE templateTLE, final double dt) {
this.dt = dt;
// Conversion of template TLE to a field TLE
final Field<Gradient> field = GradientField.getField(7);
this.templateTLE = new FieldTLE<>(field, templateTLE.getLine1(), templateTLE.getLine2(), utc);
}
/** {@inheritDoc} */
@Override
public Pair<RealVector, RealMatrix> value(final RealVector point) {
final RealVector objectiveOscState = MatrixUtils.createRealVector(6);
final RealMatrix objectiveJacobian = MatrixUtils.createRealMatrix(6, 7);
getTransformedAndJacobian(state -> meanStateToPV(state), point, objectiveOscState, objectiveJacobian);
return new Pair<>(objectiveOscState, objectiveJacobian);
}
/**
* Fill model.
* @param operator state vector propagation
* @param state state vector
* @param transformed value to fill
* @param jacobian Jacobian to fill
*/
private void getTransformedAndJacobian(final UnaryOperator<FieldVector<Gradient>> operator,
final RealVector state, final RealVector transformed,
final RealMatrix jacobian) {
// State dimension
final int stateDim = state.getDimension();
// Initialise the state as field to calculate the gradient
final GradientField field = GradientField.getField(stateDim);
final FieldVector<Gradient> fieldState = MatrixUtils.createFieldVector(field, stateDim);
for (int i = 0; i < stateDim; ++i) {
fieldState.setEntry(i, Gradient.variable(stateDim, i, state.getEntry(i)));
}
// Call operator
final FieldVector<Gradient> fieldTransformed = operator.apply(fieldState);
// Output dimension
final int outDim = fieldTransformed.getDimension();
// Extract transform and Jacobian as real values
for (int i = 0; i < outDim; ++i) {
transformed.setEntry(i, fieldTransformed.getEntry(i).getReal());
jacobian.setRow(i, fieldTransformed.getEntry(i).getGradient());
}
}
/**
* Operator to propagate the state vector.
* @param state state vector
* @return propagated state vector
*/
private FieldVector<Gradient> meanStateToPV(final FieldVector<Gradient> state) {
// Epoch
final FieldAbsoluteDate<Gradient> epoch = templateTLE.getDate();
// B*
final Gradient[] bStar = state.getSubVector(6, 1).toArray();
// Field
final Field<Gradient> field = epoch.getField();
// Extract mean state
final FieldVector3D<Gradient> position = new FieldVector3D<>(state.getSubVector(0, 3).toArray());
final FieldVector3D<Gradient> velocity = new FieldVector3D<>(state.getSubVector(3, 3).toArray());
final FieldPVCoordinates<Gradient> pvCoordinates = new FieldPVCoordinates<>(position, velocity);
final FieldKeplerianOrbit<Gradient> orbit = new FieldKeplerianOrbit<>(pvCoordinates, teme, epoch, field.getZero().newInstance(TLEPropagator.getMU()));
// Convert to TLE
final FieldTLE<Gradient> tle = TleGenerationUtil.newTLE(orbit, templateTLE, bStar[0], utc);
// Propagate to epoch
final FieldPVCoordinates<Gradient> propagatedCoordinates =
FieldTLEPropagator.selectExtrapolator(tle, teme, bStar).getPVCoordinates(epoch.shiftedBy(dt), bStar);
// Osculating
final FieldVector<Gradient> osculating = MatrixUtils.createFieldVector(field, 6);
osculating.setEntry(0, propagatedCoordinates.getPosition().getX());
osculating.setEntry(1, propagatedCoordinates.getPosition().getY());
osculating.setEntry(2, propagatedCoordinates.getPosition().getZ());
osculating.setEntry(3, propagatedCoordinates.getVelocity().getX());
osculating.setEntry(4, propagatedCoordinates.getVelocity().getY());
osculating.setEntry(5, propagatedCoordinates.getVelocity().getZ());
// Return
return osculating;
}
}
}