DSSTHarvester.java
/* Copyright 2002-2024 CS GROUP
* 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.
* CS 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.semianalytical.dsst;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import org.hipparchus.analysis.differentiation.Gradient;
import org.hipparchus.linear.MatrixUtils;
import org.hipparchus.linear.RealMatrix;
import org.orekit.orbits.OrbitType;
import org.orekit.orbits.PositionAngleType;
import org.orekit.propagation.AbstractMatricesHarvester;
import org.orekit.propagation.FieldSpacecraftState;
import org.orekit.propagation.PropagationType;
import org.orekit.propagation.SpacecraftState;
import org.orekit.propagation.semianalytical.dsst.forces.DSSTForceModel;
import org.orekit.propagation.semianalytical.dsst.forces.FieldShortPeriodTerms;
import org.orekit.propagation.semianalytical.dsst.utilities.FieldAuxiliaryElements;
import org.orekit.utils.DoubleArrayDictionary;
import org.orekit.utils.ParameterDriver;
import org.orekit.utils.TimeSpanMap;
import org.orekit.utils.TimeSpanMap.Span;
/** Harvester between two-dimensional Jacobian matrices and one-dimensional {@link
* SpacecraftState#getAdditionalState(String) additional state arrays}.
* @author Luc Maisonobe
* @author Bryan Cazabonne
* @since 11.1
*/
public class DSSTHarvester extends AbstractMatricesHarvester {
/** Retrograde factor I.
* <p>
* DSST model needs equinoctial orbit as internal representation.
* Classical equinoctial elements have discontinuities when inclination
* is close to zero. In this representation, I = +1. <br>
* To avoid this discontinuity, another representation exists and equinoctial
* elements can be expressed in a different way, called "retrograde" orbit.
* This implies I = -1. <br>
* As Orekit doesn't implement the retrograde orbit, I is always set to +1.
* But for the sake of consistency with the theory, the retrograde factor
* has been kept in the formulas.
* </p>
*/
private static final int I = 1;
/** Propagator bound to this harvester. */
private final DSSTPropagator propagator;
/** Derivatives of the short period terms that apply to State Transition Matrix.*/
private final double[][] shortPeriodDerivativesStm;
/** Derivatives of the short period terms that apply to Jacobians columns. */
private final DoubleArrayDictionary shortPeriodDerivativesJacobianColumns;
/** Columns names for parameters. */
private List<String> columnsNames;
/**
* Field short periodic terms. Key is the force model to which they pertain. Value is
* the terms. They need to be stored in a map because the DsstForceModel interface
* does not have a getter for the terms.
*/
private final Map<DSSTForceModel, List<FieldShortPeriodTerms<Gradient>>>
fieldShortPeriodTerms;
/** Simple constructor.
* <p>
* The arguments for initial matrices <em>must</em> be compatible with the
* {@link org.orekit.orbits.OrbitType#EQUINOCTIAL equinoctial orbit type}
* and {@link PositionAngleType position angle} that will be used by propagator
* </p>
* @param propagator propagator bound to this harvester
* @param stmName State Transition Matrix state name
* @param initialStm initial State Transition Matrix ∂Y/∂Y₀,
* if null (which is the most frequent case), assumed to be 6x6 identity
* @param initialJacobianColumns initial columns of the Jacobians matrix with respect to parameters,
* if null or if some selected parameters are missing from the dictionary, the corresponding
* initial column is assumed to be 0
*/
DSSTHarvester(final DSSTPropagator propagator, final String stmName,
final RealMatrix initialStm, final DoubleArrayDictionary initialJacobianColumns) {
super(stmName, initialStm, initialJacobianColumns);
this.propagator = propagator;
this.shortPeriodDerivativesStm = new double[STATE_DIMENSION][STATE_DIMENSION];
this.shortPeriodDerivativesJacobianColumns = new DoubleArrayDictionary();
// Use identity hash map to have the same behavior as a getter on the force model
this.fieldShortPeriodTerms = new IdentityHashMap<>();
}
/** {@inheritDoc} */
@Override
public RealMatrix getStateTransitionMatrix(final SpacecraftState state) {
final RealMatrix stm = super.getStateTransitionMatrix(state);
if (propagator.getPropagationType() == PropagationType.OSCULATING) {
// add the short period terms
for (int i = 0; i < STATE_DIMENSION; i++) {
for (int j = 0; j < STATE_DIMENSION; j++) {
stm.addToEntry(i, j, shortPeriodDerivativesStm[i][j]);
}
}
}
return stm;
}
/** {@inheritDoc} */
@Override
public RealMatrix getParametersJacobian(final SpacecraftState state) {
final RealMatrix jacobian = super.getParametersJacobian(state);
if (jacobian != null && propagator.getPropagationType() == PropagationType.OSCULATING) {
// add the short period terms
final List<String> names = getJacobiansColumnsNames();
for (int j = 0; j < names.size(); ++j) {
final double[] column = shortPeriodDerivativesJacobianColumns.get(names.get(j));
for (int i = 0; i < STATE_DIMENSION; i++) {
jacobian.addToEntry(i, j, column[i]);
}
}
}
return jacobian;
}
/** Get the Jacobian matrix B1 (B1 = ∂εη/∂Y).
* <p>
* B1 represents the partial derivatives of the short period motion
* with respect to the mean equinoctial elements.
* </p>
* @return the B1 jacobian matrix
*/
public RealMatrix getB1() {
// Initialize B1
final RealMatrix B1 = MatrixUtils.createRealMatrix(STATE_DIMENSION, STATE_DIMENSION);
// add the short period terms
for (int i = 0; i < STATE_DIMENSION; i++) {
for (int j = 0; j < STATE_DIMENSION; j++) {
B1.addToEntry(i, j, shortPeriodDerivativesStm[i][j]);
}
}
// Return B1
return B1;
}
/** Get the Jacobian matrix B2 (B2 = ∂Y/∂Y₀).
* <p>
* B2 represents the partial derivatives of the mean equinoctial elements
* with respect to the initial ones.
* </p>
* @param state spacecraft state
* @return the B2 jacobian matrix
*/
public RealMatrix getB2(final SpacecraftState state) {
return super.getStateTransitionMatrix(state);
}
/** Get the Jacobian matrix B3 (B3 = ∂Y/∂P).
* <p>
* B3 represents the partial derivatives of the mean equinoctial elements
* with respect to the estimated propagation parameters.
* </p>
* @param state spacecraft state
* @return the B3 jacobian matrix
*/
public RealMatrix getB3(final SpacecraftState state) {
return super.getParametersJacobian(state);
}
/** Get the Jacobian matrix B4 (B4 = ∂εη/∂c).
* <p>
* B4 represents the partial derivatives of the short period motion
* with respect to the estimated propagation parameters.
* </p>
* @return the B4 jacobian matrix
*/
public RealMatrix getB4() {
// Initialize B4
final List<String> names = getJacobiansColumnsNames();
final RealMatrix B4 = MatrixUtils.createRealMatrix(STATE_DIMENSION, names.size());
// add the short period terms
for (int j = 0; j < names.size(); ++j) {
final double[] column = shortPeriodDerivativesJacobianColumns.get(names.get(j));
for (int i = 0; i < STATE_DIMENSION; i++) {
B4.addToEntry(i, j, column[i]);
}
}
// Return B4
return B4;
}
/** Freeze the names of the Jacobian columns.
* <p>
* This method is called when proagation starts, i.e. when configuration is completed
* </p>
*/
public void freezeColumnsNames() {
columnsNames = getJacobiansColumnsNames();
}
/** {@inheritDoc} */
@Override
public List<String> getJacobiansColumnsNames() {
return columnsNames == null ? propagator.getJacobiansColumnsNames() : columnsNames;
}
/** Initialize the short periodic terms for the "field" elements.
* @param reference current mean spacecraft state
*/
public void initializeFieldShortPeriodTerms(final SpacecraftState reference) {
initializeFieldShortPeriodTerms(reference, propagator.getPropagationType());
}
/**
* Initialize the short periodic terms for the "field" elements.
*
* @param reference current mean spacecraft state
* @param type MEAN or OSCULATING
*/
public void initializeFieldShortPeriodTerms(final SpacecraftState reference,
final PropagationType type) {
// Converter
final DSSTGradientConverter converter = new DSSTGradientConverter(reference, propagator.getAttitudeProvider());
// clear old values
// prevents duplicates or stale values when reusing a DSSTPropagator
fieldShortPeriodTerms.clear();
// Loop on force models
for (final DSSTForceModel forceModel : propagator.getAllForceModels()) {
// Convert to Gradient
final FieldSpacecraftState<Gradient> dsState = converter.getState(forceModel);
final Gradient[] dsParameters = converter.getParametersAtStateDate(dsState, forceModel);
final FieldAuxiliaryElements<Gradient> auxiliaryElements = new FieldAuxiliaryElements<>(dsState.getOrbit(), I);
// Initialize the "Field" short periodic terms, same mode as the propagator
final List<FieldShortPeriodTerms<Gradient>> terms =
forceModel.initializeShortPeriodTerms(
auxiliaryElements,
type,
dsParameters);
// create a copy of the list to protect against inadvertent modification
final List<FieldShortPeriodTerms<Gradient>> list;
synchronized (fieldShortPeriodTerms) {
list = fieldShortPeriodTerms.computeIfAbsent(forceModel, x -> new ArrayList<>());
}
list.addAll(terms);
}
}
/** Update the short periodic terms for the "field" elements.
* @param reference current mean spacecraft state
*/
@SuppressWarnings("unchecked")
public void updateFieldShortPeriodTerms(final SpacecraftState reference) {
// Converter
final DSSTGradientConverter converter = new DSSTGradientConverter(reference, propagator.getAttitudeProvider());
// Loop on force models
for (final DSSTForceModel forceModel : propagator.getAllForceModels()) {
// Convert to Gradient
final FieldSpacecraftState<Gradient> dsState = converter.getState(forceModel);
final Gradient[] dsParameters = converter.getParameters(dsState, forceModel);
// Update the short periodic terms for the current force model
forceModel.updateShortPeriodTerms(dsParameters, dsState);
}
}
/** {@inheritDoc} */
@Override
public void setReferenceState(final SpacecraftState reference) {
// reset derivatives to zero
for (final double[] row : shortPeriodDerivativesStm) {
Arrays.fill(row, 0.0);
}
shortPeriodDerivativesJacobianColumns.clear();
final DSSTGradientConverter converter = new DSSTGradientConverter(reference, propagator.getAttitudeProvider());
// Compute Jacobian
for (final DSSTForceModel forceModel : propagator.getAllForceModels()) {
final FieldSpacecraftState<Gradient> dsState = converter.getState(forceModel);
final Gradient zero = dsState.getDate().getField().getZero();
final Gradient[] shortPeriod = new Gradient[6];
Arrays.fill(shortPeriod, zero);
final List<FieldShortPeriodTerms<Gradient>> terms;
synchronized (fieldShortPeriodTerms) {
terms = fieldShortPeriodTerms.computeIfAbsent(forceModel, x -> new ArrayList<>(0));
}
for (final FieldShortPeriodTerms<Gradient> spt : terms) {
final Gradient[] spVariation = spt.value(dsState.getOrbit());
for (int i = 0; i < spVariation .length; i++) {
shortPeriod[i] = shortPeriod[i].add(spVariation[i]);
}
}
final double[] derivativesASP = shortPeriod[0].getGradient();
final double[] derivativesExSP = shortPeriod[1].getGradient();
final double[] derivativesEySP = shortPeriod[2].getGradient();
final double[] derivativesHxSP = shortPeriod[3].getGradient();
final double[] derivativesHySP = shortPeriod[4].getGradient();
final double[] derivativesLSP = shortPeriod[5].getGradient();
// update Jacobian with respect to state
addToRow(derivativesASP, 0);
addToRow(derivativesExSP, 1);
addToRow(derivativesEySP, 2);
addToRow(derivativesHxSP, 3);
addToRow(derivativesHySP, 4);
addToRow(derivativesLSP, 5);
int paramsIndex = converter.getFreeStateParameters();
for (ParameterDriver driver : forceModel.getParametersDrivers()) {
if (driver.isSelected()) {
final TimeSpanMap<String> driverNameSpanMap = driver.getNamesSpanMap();
// for each span (for each estimated value) corresponding name is added
for (Span<String> span = driverNameSpanMap.getFirstSpan(); span != null; span = span.next()) {
// get the partials derivatives for this driver
DoubleArrayDictionary.Entry entry = shortPeriodDerivativesJacobianColumns.getEntry(span.getData());
if (entry == null) {
// create an entry filled with zeroes
shortPeriodDerivativesJacobianColumns.put(span.getData(), new double[STATE_DIMENSION]);
entry = shortPeriodDerivativesJacobianColumns.getEntry(span.getData());
}
// add the contribution of the current force model
entry.increment(new double[] {
derivativesASP[paramsIndex], derivativesExSP[paramsIndex], derivativesEySP[paramsIndex],
derivativesHxSP[paramsIndex], derivativesHySP[paramsIndex], derivativesLSP[paramsIndex]
});
++paramsIndex;
}
}
}
}
}
/** Fill State Transition Matrix rows.
* @param derivatives derivatives of a component
* @param index component index (0 for a, 1 for ex, 2 for ey, 3 for hx, 4 for hy, 5 for l)
*/
private void addToRow(final double[] derivatives, final int index) {
for (int i = 0; i < 6; i++) {
shortPeriodDerivativesStm[index][i] += derivatives[i];
}
}
/** {@inheritDoc} */
@Override
public OrbitType getOrbitType() {
return propagator.getOrbitType();
}
/** {@inheritDoc} */
@Override
public PositionAngleType getPositionAngleType() {
return propagator.getPositionAngleType();
}
}