AbstractFixedInitialCartesianSingleShooting.java

  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.control.indirect.shooting;

  18. import org.hipparchus.CalculusFieldElement;
  19. import org.hipparchus.Field;
  20. import org.hipparchus.analysis.differentiation.Gradient;
  21. import org.hipparchus.analysis.differentiation.GradientField;
  22. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  23. import org.hipparchus.linear.LUDecomposition;
  24. import org.hipparchus.linear.MatrixUtils;
  25. import org.hipparchus.linear.RealMatrix;
  26. import org.hipparchus.ode.FieldOrdinaryDifferentialEquation;
  27. import org.hipparchus.ode.nonstiff.FieldExplicitRungeKuttaIntegrator;
  28. import org.hipparchus.util.FastMath;
  29. import org.hipparchus.util.MathArrays;
  30. import org.orekit.attitudes.FieldAttitude;
  31. import org.orekit.control.indirect.adjoint.CartesianAdjointDerivativesProvider;
  32. import org.orekit.control.indirect.adjoint.FieldCartesianAdjointDerivativesProvider;
  33. import org.orekit.control.indirect.adjoint.cost.ControlSwitchDetector;
  34. import org.orekit.control.indirect.adjoint.cost.FieldControlSwitchDetector;
  35. import org.orekit.control.indirect.shooting.propagation.AdjointDynamicsProvider;
  36. import org.orekit.control.indirect.shooting.propagation.ShootingPropagationSettings;
  37. import org.orekit.forces.ForceModel;
  38. import org.orekit.frames.Frame;
  39. import org.orekit.orbits.FieldCartesianOrbit;
  40. import org.orekit.propagation.FieldSpacecraftState;
  41. import org.orekit.propagation.SpacecraftState;
  42. import org.orekit.propagation.events.DateDetector;
  43. import org.orekit.propagation.events.EventsLogger;
  44. import org.orekit.propagation.events.FieldEventDetector;
  45. import org.orekit.propagation.integration.FieldAdditionalDerivativesProvider;
  46. import org.orekit.propagation.integration.FieldCombinedDerivatives;
  47. import org.orekit.propagation.numerical.NumericalPropagator;
  48. import org.orekit.propagation.sampling.PropagationStepRecorder;
  49. import org.orekit.time.AbsoluteDate;
  50. import org.orekit.time.FieldAbsoluteDate;
  51. import org.orekit.utils.FieldAbsolutePVCoordinates;
  52. import org.orekit.utils.FieldPVCoordinates;

  53. import java.util.Arrays;
  54. import java.util.List;
  55. import java.util.stream.Collectors;

  56. /**
  57.  * Abstract class for indirect single shooting methods with fixed initial Cartesian state.
  58.  * Inheritors must implement the iteration update, assuming derivatives are needed.
  59.  *
  60.  * @author Romain Serra
  61.  * @since 13.0
  62.  * @see CartesianAdjointDerivativesProvider
  63.  * @see org.orekit.control.indirect.adjoint.FieldCartesianAdjointDerivativesProvider
  64.  */
  65. public abstract class AbstractFixedInitialCartesianSingleShooting extends AbstractIndirectShooting {

  66.     /** Template for initial state. Contains the initial Cartesian coordinates. */
  67.     private final SpacecraftState initialSpacecraftStateTemplate;

  68.     /**
  69.      * Step handler to record propagation steps.
  70.      */
  71.     private final PropagationStepRecorder propagationStepRecorder;

  72.     /** Event logger. */
  73.     private final EventsLogger eventsLogger;

  74.     /** Scales for automatic differentiation variables. */
  75.     private double[] scales;

  76.     /**
  77.      * Constructor with boundary conditions as orbits.
  78.      * @param propagationSettings propagation settings
  79.      * @param initialSpacecraftStateTemplate template for initial propagation state
  80.      */
  81.     protected AbstractFixedInitialCartesianSingleShooting(final ShootingPropagationSettings propagationSettings,
  82.                                                           final SpacecraftState initialSpacecraftStateTemplate) {
  83.         super(propagationSettings);
  84.         this.initialSpacecraftStateTemplate = initialSpacecraftStateTemplate;
  85.         this.propagationStepRecorder = new PropagationStepRecorder();
  86.         this.eventsLogger = new EventsLogger();
  87.     }

  88.     /**
  89.      * Build unit scales.
  90.      * @return scales
  91.      */
  92.     private double[] getUnitScales() {
  93.         final double[] unitScales = new double[getPropagationSettings().getAdjointDynamicsProvider().getDimension()];
  94.         Arrays.fill(unitScales, 1.);
  95.         return unitScales;
  96.     }

  97.     /**
  98.      * Protected getter for the differentiation scales.
  99.      * @return scales for variable scales
  100.      */
  101.     protected double[] getScales() {
  102.         return scales;
  103.     }

  104.     /**
  105.      * Returns the maximum number of iterations.
  106.      * @return maximum iterations
  107.      */
  108.     public abstract int getMaximumIterationCount();

  109.     /** {@inheritDoc} */
  110.     @Override
  111.     public ShootingBoundaryOutput solve(final double initialMass, final double[] initialGuess) {
  112.         return solve(initialMass, initialGuess, getUnitScales());
  113.     }

  114.     /**
  115.      * Solve for the boundary conditions, given an initial mass and an initial guess for the adjoint variables.
  116.      * Uses scales for automatic differentiation.
  117.      * @param initialMass initial mass
  118.      * @param initialGuess initial guess
  119.      * @param userScales scales
  120.      * @return boundary problem solution
  121.      */
  122.     public ShootingBoundaryOutput solve(final double initialMass, final double[] initialGuess,
  123.                                         final double[] userScales) {
  124.         scales = userScales.clone();
  125.         final SpacecraftState initialState = createStateWithMassAndAdjoint(initialMass, initialGuess);
  126.         final ShootingBoundaryOutput initialGuessSolution = computeCandidateSolution(initialState, 0);
  127.         if (initialGuessSolution.isConverged()) {
  128.             return initialGuessSolution;
  129.         } else {
  130.             return iterate(initialGuessSolution);
  131.         }
  132.     }

  133.     /** {@inheritDoc} */
  134.     @Override
  135.     protected NumericalPropagator buildPropagator(final SpacecraftState initialState) {
  136.         final NumericalPropagator propagator = super.buildPropagator(initialState);
  137.         propagator.setStepHandler(propagationStepRecorder);
  138.         final CartesianAdjointDerivativesProvider derivativesProvider = (CartesianAdjointDerivativesProvider)
  139.             getPropagationSettings().getAdjointDynamicsProvider().buildAdditionalDerivativesProvider();
  140.         eventsLogger.clearLoggedEvents();
  141.         derivativesProvider.getCost().getEventDetectors()
  142.                 .forEach(detector -> propagator.addEventDetector(eventsLogger.monitorDetector(detector)));
  143.         return propagator;
  144.     }

  145.     /**
  146.      * Form solution container with input initial state.
  147.      * @param initialState initial state
  148.      * @param iterationCount iteration count
  149.      * @return candidate solution
  150.      */
  151.     public abstract ShootingBoundaryOutput computeCandidateSolution(SpacecraftState initialState, int iterationCount);

  152.     /**
  153.      * Iterate on initial guess to solve boundary problem.
  154.      * @param initialGuess initial guess
  155.      * @return solution (or null pointer if not converged)
  156.      */
  157.     private ShootingBoundaryOutput iterate(final ShootingBoundaryOutput initialGuess) {
  158.         int iterationCount = 1;
  159.         SpacecraftState initialState = initialGuess.getInitialState();
  160.         ShootingBoundaryOutput candidateSolution = initialGuess;
  161.         final String adjointName = getPropagationSettings().getAdjointDynamicsProvider().getAdjointName();
  162.         double[] initialAdjoint = initialState.getAdditionalState(adjointName).clone();
  163.         final double initialMass = initialState.getMass();
  164.         while (iterationCount < getMaximumIterationCount()) {
  165.             if (candidateSolution.isConverged()) {
  166.                 return candidateSolution;
  167.             }
  168.             final FieldSpacecraftState<Gradient> fieldInitialState = createFieldInitialStateWithMassAndAdjoint(initialMass,
  169.                 initialAdjoint);
  170.             initialState = fieldInitialState.toSpacecraftState();
  171.             final FieldSpacecraftState<Gradient> fieldTerminalState = propagateField(fieldInitialState);
  172.             initialAdjoint = updateShootingVariables(initialAdjoint, fieldTerminalState);
  173.             if (Double.isNaN(initialAdjoint[0])) {
  174.                 return candidateSolution;
  175.             } else {
  176.                 candidateSolution = computeCandidateSolution(initialState, iterationCount);
  177.             }
  178.             iterationCount++;
  179.         }
  180.         return candidateSolution;
  181.     }

  182.     /**
  183.      * Propagate in Field. Does not use a full propagator (only the integrator, moving step by step using the history of non-Field propagation).
  184.      * It is faster as adaptive step and event detection are particularly slow with Field.
  185.      * @param fieldInitialState initial state
  186.      * @return terminal state
  187.      */
  188.     private FieldSpacecraftState<Gradient> propagateField(final FieldSpacecraftState<Gradient> fieldInitialState) {
  189.         final FieldAbsoluteDate<Gradient> initialDate = fieldInitialState.getDate();
  190.         final FieldExplicitRungeKuttaIntegrator<Gradient> fieldIntegrator = buildFieldIntegrator(fieldInitialState);
  191.         final FieldOrdinaryDifferentialEquation<Gradient> fieldODE = buildFieldODE(fieldInitialState.getDate());
  192.         final AdjointDynamicsProvider dynamicsProvider = getPropagationSettings().getAdjointDynamicsProvider();
  193.         AbsoluteDate date = initialDate.toAbsoluteDate();
  194.         Gradient[] integrationState = formatToArray(fieldInitialState, dynamicsProvider.getAdjointName());
  195.         // step-by-step integration
  196.         final List<EventsLogger.LoggedEvent> loggedEvents = eventsLogger.getLoggedEvents();
  197.         final List<AbsoluteDate> stepDates = propagationStepRecorder.copyStates().stream().map(SpacecraftState::getDate)
  198.                 .collect(Collectors.toList());
  199.         for (final AbsoluteDate stepDate: stepDates) {
  200.             final Gradient time = initialDate.durationFrom(date).negate();
  201.             final Gradient nextTime = initialDate.durationFrom(stepDate).negate();
  202.             integrationState = fieldIntegrator.singleStep(fieldODE, time, integrationState, nextTime);
  203.             // deal with switch event if any
  204.             if (!loggedEvents.isEmpty()) {
  205.                 for (final EventsLogger.LoggedEvent loggedEvent: loggedEvents) {
  206.                     final SpacecraftState loggedState = loggedEvent.getState();
  207.                     if (loggedEvent.getEventDetector() instanceof ControlSwitchDetector && loggedState.getDate().isEqualTo(stepDate)) {
  208.                         final ControlSwitchDetector switchDetector = (ControlSwitchDetector) loggedEvent.getEventDetector();
  209.                         integrationState = findSwitchEventAndUpdateState(date, integrationState,
  210.                                 initialDate.toAbsoluteDate(), switchDetector, dynamicsProvider);
  211.                     }
  212.                 }
  213.             }
  214.             date = new AbsoluteDate(stepDate);
  215.         }
  216.         return formatFromArray(date, integrationState);
  217.     }

  218.     /**
  219.      * Create initial state with input mass.
  220.      * @param initialMass initial mass
  221.      * @return state
  222.      */
  223.     protected SpacecraftState createInitialStateWithMass(final double initialMass) {
  224.         return initialSpacecraftStateTemplate.withMass(initialMass);
  225.     }

  226.     /**
  227.      * Create initial state with input mass and adjoint vector.
  228.      * @param initialMass initial mass
  229.      * @param initialAdjoint initial adjoint variables
  230.      * @return state
  231.      */
  232.     private SpacecraftState createStateWithMassAndAdjoint(final double initialMass, final double[] initialAdjoint) {
  233.         final String adjointName = getPropagationSettings().getAdjointDynamicsProvider().getAdjointName();
  234.         return createInitialStateWithMass(initialMass).addAdditionalData(adjointName, initialAdjoint);
  235.     }

  236.     /**
  237.      * Create initial Gradient state with input mass and adjoint vector, the latter being the independent variables.
  238.      * @param initialMass initial mass
  239.      * @param initialAdjoint initial adjoint variables
  240.      * @return state
  241.      */
  242.     protected FieldSpacecraftState<Gradient> createFieldInitialStateWithMassAndAdjoint(final double initialMass,
  243.                                                                                        final double[] initialAdjoint) {
  244.         final int parametersNumber = initialAdjoint.length;
  245.         final GradientField field = GradientField.getField(parametersNumber);
  246.         final FieldSpacecraftState<Gradient> stateWithoutAdjoint = new FieldSpacecraftState<>(field,
  247.                 createInitialStateWithMass(initialMass));
  248.         final Gradient[] fieldInitialAdjoint = MathArrays.buildArray(field, initialAdjoint.length);
  249.         for (int i = 0; i < parametersNumber; i++) {
  250.             fieldInitialAdjoint[i] = Gradient.variable(parametersNumber, i, 0.).multiply(getScales()[i]).add(initialAdjoint[i]);
  251.         }
  252.         final String adjointName = getPropagationSettings().getAdjointDynamicsProvider().getAdjointName();
  253.         return stateWithoutAdjoint.addAdditionalData(adjointName, fieldInitialAdjoint);
  254.     }

  255.     /**
  256.      * Create state.
  257.      * @param date epoch
  258.      * @param cartesian Cartesian variables
  259.      * @param mass mass
  260.      * @param adjoint adjoint variables
  261.      * @param <T> field type
  262.      * @return state
  263.      */
  264.     protected <T extends CalculusFieldElement<T>> FieldSpacecraftState<T> createFieldState(final FieldAbsoluteDate<T> date,
  265.                                                                                            final T[] cartesian,
  266.                                                                                            final T mass,
  267.                                                                                            final T[] adjoint) {
  268.         final Field<T> field = mass.getField();
  269.         final Frame frame = getPropagationSettings().getPropagationFrame();
  270.         final FieldVector3D<T> position = new FieldVector3D<>(Arrays.copyOfRange(cartesian, 0, 3));
  271.         final FieldVector3D<T> velocity = new FieldVector3D<>(Arrays.copyOfRange(cartesian, 3, 6));
  272.         final FieldPVCoordinates<T> pvCoordinates = new FieldPVCoordinates<>(position, velocity);
  273.         final FieldSpacecraftState<T> stateWithoutAdjoint;
  274.         if (initialSpacecraftStateTemplate.isOrbitDefined()) {
  275.             final T mu = field.getZero().newInstance(initialSpacecraftStateTemplate.getOrbit().getMu());
  276.             final FieldCartesianOrbit<T> orbit = new FieldCartesianOrbit<>(pvCoordinates, frame, date, mu);
  277.             final FieldAttitude<T> attitude = getPropagationSettings().getAttitudeProvider().getAttitude(orbit,
  278.                     orbit.getDate(), orbit.getFrame());
  279.             stateWithoutAdjoint = new FieldSpacecraftState<>(orbit, attitude).withMass(mass);
  280.         } else {
  281.             final FieldAbsolutePVCoordinates<T> absolutePVCoordinates = new FieldAbsolutePVCoordinates<>(frame, date,
  282.                     pvCoordinates);
  283.             final FieldAttitude<T> attitude = getPropagationSettings().getAttitudeProvider().getAttitude(absolutePVCoordinates,
  284.                     absolutePVCoordinates.getDate(), absolutePVCoordinates.getFrame());
  285.             stateWithoutAdjoint = new FieldSpacecraftState<>(absolutePVCoordinates, attitude).withMass(mass);
  286.         }
  287.         final String adjointName = getPropagationSettings().getAdjointDynamicsProvider().getAdjointName();
  288.         return stateWithoutAdjoint.addAdditionalData(adjointName, adjoint);
  289.     }

  290.     /**
  291.      * Update shooting variables.
  292.      * @param originalShootingVariables original shooting variables (before update)
  293.      * @param fieldTerminalState final state of gradient propagation
  294.      * @return updated shooting variables
  295.      */
  296.     protected abstract double[] updateShootingVariables(double[] originalShootingVariables,
  297.                                                         FieldSpacecraftState<Gradient> fieldTerminalState);

  298.     /**
  299.      * Build Ordinary Differential Equation for Field.
  300.      * @param referenceDate date defining the origin of times
  301.      * @param <T> field type
  302.      * @return Field ODE
  303.      * @since 13.0
  304.      */
  305.     protected <T extends CalculusFieldElement<T>> FieldOrdinaryDifferentialEquation<T> buildFieldODE(final FieldAbsoluteDate<T> referenceDate) {
  306.         final Field<T> field = referenceDate.getField();
  307.         final AdjointDynamicsProvider adjointDynamicsProvider = getPropagationSettings().getAdjointDynamicsProvider();
  308.         final FieldAdditionalDerivativesProvider<T> derivativesProvider = adjointDynamicsProvider
  309.                 .buildFieldAdditionalDerivativesProvider(field);

  310.         return new FieldOrdinaryDifferentialEquation<T>() {
  311.             @Override
  312.             public int getDimension() {
  313.                 return 7 + adjointDynamicsProvider.getDimension();
  314.             }

  315.             @Override
  316.             public T[] computeDerivatives(final T t, final T[] y) {
  317.                 // build state
  318.                 final T[] cartesian = Arrays.copyOfRange(y, 0, 6);
  319.                 final FieldAbsoluteDate<T> date = referenceDate.shiftedBy(t);
  320.                 final Field<T> field = date.getField();
  321.                 final T zero = field.getZero();
  322.                 final T[] adjoint = Arrays.copyOfRange(y, 7, y.length);
  323.                 final FieldSpacecraftState<T> state = createFieldState(date, cartesian, y[6], adjoint);
  324.                 // compute derivatives
  325.                 final T[] yDot = MathArrays.buildArray(field, getDimension());
  326.                 yDot[0] = zero.add(y[3]);
  327.                 yDot[1] = zero.add(y[4]);
  328.                 yDot[2] = zero.add(y[5]);
  329.                 FieldVector3D<T> totalAcceleration = FieldVector3D.getZero(field);
  330.                 for (final ForceModel forceModel: getPropagationSettings().getForceModels()) {
  331.                     final FieldVector3D<T> acceleration = forceModel.acceleration(state, forceModel.getParameters(field));
  332.                     totalAcceleration = totalAcceleration.add(acceleration);
  333.                 }
  334.                 yDot[3] = totalAcceleration.getX();
  335.                 yDot[4] = totalAcceleration.getY();
  336.                 yDot[5] = totalAcceleration.getZ();
  337.                 final FieldCombinedDerivatives<T> combinedDerivatives = derivativesProvider.combinedDerivatives(state);
  338.                 final T[] cartesianDotContribution = combinedDerivatives.getMainStateDerivativesIncrements();
  339.                 for (int i = 0; i < cartesianDotContribution.length; i++) {
  340.                     yDot[i] = yDot[i].add(cartesianDotContribution[i]);
  341.                 }
  342.                 final T[] adjointDot = combinedDerivatives.getAdditionalDerivatives();
  343.                 System.arraycopy(adjointDot, 0, yDot, yDot.length - adjointDot.length, adjointDot.length);
  344.                 return yDot;
  345.             }
  346.         };
  347.     }

  348.     /**
  349.      * Form array from Orekit object.
  350.      * @param fieldState state
  351.      * @param adjointName adjoint name
  352.      * @return propagation state as array
  353.      */
  354.     private Gradient[] formatToArray(final FieldSpacecraftState<Gradient> fieldState,
  355.                                      final String adjointName) {
  356.         final Gradient[] adjoint = fieldState.getAdditionalState(adjointName);
  357.         final int adjointDimension = adjoint.length;
  358.         final Gradient[] integrationState = MathArrays.buildArray(fieldState.getMass().getField(), 7 + adjointDimension);
  359.         final FieldPVCoordinates<Gradient> pvCoordinates = fieldState.getPVCoordinates();
  360.         System.arraycopy(pvCoordinates.getPosition().toArray(), 0, integrationState, 0, 3);
  361.         System.arraycopy(pvCoordinates.getVelocity().toArray(), 0, integrationState, 3, 3);
  362.         integrationState[6] = fieldState.getMass();
  363.         System.arraycopy(adjoint, 0, integrationState, 7, adjointDimension);
  364.         return integrationState;
  365.     }

  366.     /**
  367.      * Form Orekit object from array.
  368.      * @param date date
  369.      * @param integrationState propagation state as array
  370.      * @return Orekit state
  371.      */
  372.     private FieldSpacecraftState<Gradient> formatFromArray(final AbsoluteDate date, final Gradient[] integrationState) {
  373.         final Gradient[] terminalCartesian = Arrays.copyOfRange(integrationState, 0, 6);
  374.         final Gradient[] terminalAdjoint = Arrays.copyOfRange(integrationState, 7, integrationState.length);
  375.         final Field<Gradient> field = terminalCartesian[0].getField();
  376.         return createFieldState(new FieldAbsoluteDate<>(field, date), terminalCartesian, integrationState[6],
  377.                 terminalAdjoint);
  378.     }

  379.     /**
  380.      * Iterate over field switch detectors and find the one that was triggered in the non-Field propagation.
  381.      * Then, use it to update the gradient.
  382.      * @param date date
  383.      * @param integrationState integration variables
  384.      * @param referenceDate date at start of propagation
  385.      * @param switchDetector switch detector
  386.      * @param dynamicsProvider adjoint dynamics provider
  387.      * @return update integration state
  388.      */
  389.     private Gradient[] findSwitchEventAndUpdateState(final AbsoluteDate date, final Gradient[] integrationState,
  390.                                                      final AbsoluteDate referenceDate,
  391.                                                      final ControlSwitchDetector switchDetector,
  392.                                                      final AdjointDynamicsProvider dynamicsProvider) {
  393.         final int shootingVariables = dynamicsProvider.getDimension();
  394.         final int increasedVariables = shootingVariables + 1;
  395.         final GradientField increasedVariablesField = GradientField.getField(increasedVariables);
  396.         final FieldCartesianAdjointDerivativesProvider<Gradient> fieldDerivativesProvider =
  397.                 (FieldCartesianAdjointDerivativesProvider<Gradient>) dynamicsProvider.buildFieldAdditionalDerivativesProvider(increasedVariablesField);
  398.         final List<FieldEventDetector<Gradient>> fieldEventDetectors = fieldDerivativesProvider.getCost()
  399.                 .getFieldEventDetectors(increasedVariablesField).collect(Collectors.toList());
  400.         final FieldSpacecraftState<Gradient> fieldState = formatFromArray(date, integrationState);
  401.         final double expectedG = switchDetector.g(fieldState.toSpacecraftState());
  402.         for (final FieldEventDetector<Gradient> fieldEventDetector : fieldEventDetectors) {
  403.             if (fieldEventDetector instanceof FieldControlSwitchDetector) {
  404.                 final double actualG = fieldEventDetector.g(fieldState).getReal();
  405.                 if (FastMath.abs(actualG - expectedG) < 1e-10) {
  406.                     return updateStateWithTaylorMapInversion(date, integrationState, referenceDate,
  407.                             fieldEventDetector, dynamicsProvider.getAdjointName());
  408.                 }
  409.             }
  410.         }
  411.         return integrationState;
  412.     }

  413.     /**
  414.      * Update the differential information by taking into account that a state-dependent event was triggered.
  415.      * @param date date
  416.      * @param integrationState integration variables
  417.      * @param initialDate date at start of propagation
  418.      * @param fieldDetector switch detector
  419.      * @param adjointName adjoint name
  420.      * @return updated integration state
  421.      */
  422.     private Gradient[] updateStateWithTaylorMapInversion(final AbsoluteDate date, final Gradient[] integrationState,
  423.                                                          final AbsoluteDate initialDate,
  424.                                                          final FieldEventDetector<Gradient> fieldDetector,
  425.                                                          final String adjointName) {
  426.         // form array with increased gradient size
  427.         final Gradient threshold = fieldDetector.getThreshold();
  428.         final int increasedVariables = threshold.getFreeParameters();
  429.         final GradientField increasedVariablesField = GradientField.getField(increasedVariables);
  430.         final Gradient[] increasedVariablesArray = MathArrays.buildArray(increasedVariablesField,
  431.                 integrationState.length);
  432.         for (int i = 0; i < integrationState.length; i++) {
  433.             increasedVariablesArray[i] = integrationState[i].stackVariable();
  434.         }
  435.         final Gradient dt = Gradient.variable(increasedVariables, increasedVariables - 1, 1);
  436.         // add event function as new variable and nullify it before switch
  437.         final Gradient gBefore = evaluateG(date, increasedVariablesArray, initialDate, fieldDetector, dt, adjointName);
  438.         final Gradient[] mapBeforeSwitch = invertTaylorMap(increasedVariablesArray, gBefore);
  439.         // re-increase gradient size and slightly shift in time to pass switch
  440.         for (int i = 0; i < integrationState.length; i++) {
  441.             increasedVariablesArray[i] = mapBeforeSwitch[i].stackVariable();
  442.         }
  443.         final boolean isForward = date.isAfter(initialDate);
  444.         final double tinyStep = FastMath.min(DateDetector.DEFAULT_THRESHOLD, threshold.getValue());
  445.         final double timeShift = isForward ? tinyStep : -tinyStep;
  446.         final FieldSpacecraftState<Gradient> stateAfterSwitch = formatFromArray(date, increasedVariablesArray)
  447.                 .shiftedBy(timeShift);
  448.         final Gradient[] stateAfterSwitchArray = formatToArray(stateAfterSwitch, adjointName);
  449.         // add event function as new variable and nullify it after switch
  450.         final AbsoluteDate shiftedDate = date.shiftedBy(timeShift);
  451.         final Gradient gAfter = evaluateG(shiftedDate, stateAfterSwitchArray, initialDate, fieldDetector, dt.negate(),
  452.                 adjointName);
  453.         return invertTaylorMap(increasedVariablesArray, gAfter);
  454.     }

  455.     /**
  456.      * Evaluate event function in proper Taylor algebra.
  457.      * @param date date
  458.      * @param increasedVariablesArray integration variables with increased gradient size
  459.      * @param initialDate date at start of propagation
  460.      * @param fieldDetector switch detector
  461.      * @param dt time as independent variable
  462.      * @param adjointName adjoint name
  463.      * @return g
  464.      */
  465.     private Gradient evaluateG(final AbsoluteDate date, final Gradient[] increasedVariablesArray,
  466.                                final AbsoluteDate initialDate, final FieldEventDetector<Gradient> fieldDetector,
  467.                                final Gradient dt, final String adjointName) {
  468.         final Field<Gradient> field = increasedVariablesArray[0].getField();
  469.         final FieldAbsoluteDate<Gradient> referenceDate = new FieldAbsoluteDate<>(field, initialDate);
  470.         final FieldOrdinaryDifferentialEquation<Gradient> fieldODE = buildFieldODE(referenceDate);
  471.         final double duration = date.durationFrom(initialDate);
  472.         final Gradient[] derivatives = fieldODE.computeDerivatives(field.getZero().newInstance(duration),
  473.                 increasedVariablesArray);
  474.         final FieldSpacecraftState<Gradient> fieldState = formatFromArray(date, increasedVariablesArray);
  475.         final FieldSpacecraftState<Gradient> fieldStateWithAdjointDerivative = fieldState.addAdditionalStateDerivative(adjointName,
  476.                 Arrays.copyOfRange(derivatives, derivatives.length - 7, derivatives.length));
  477.         return fieldDetector.g(fieldStateWithAdjointDerivative.shiftedBy(dt));
  478.     }

  479.     /**
  480.      * Invert so-called Taylor map to make the event function value an independent variable.
  481.      * Then nullify its variation to keep only the derivatives of interest.
  482.      * @param state integration variables with dt as last gradient variable
  483.      * @param g event function evaluated with dt as last gradient variable
  484.      * @return update integration variables
  485.      */
  486.     private static Gradient[] invertTaylorMap(final Gradient[] state, final Gradient g) {
  487.         // swap dt and g as variables of algebra
  488.         final int increasedGradientDimension = g.getFreeParameters();
  489.         final RealMatrix rightMatrix = MatrixUtils.createRealIdentityMatrix(increasedGradientDimension);
  490.         rightMatrix.setRow(rightMatrix.getRowDimension() - 1, g.getGradient());
  491.         final LUDecomposition luDecomposition = new LUDecomposition(rightMatrix, 0.);
  492.         final RealMatrix inverted = luDecomposition.getSolver().getInverse();
  493.         final double[][] leftMatrixCoefficients = new double[state.length][];
  494.         for (int i = 0; i < state.length; i++) {
  495.             leftMatrixCoefficients[i] = state[i].getGradient();
  496.         }
  497.         final RealMatrix product = MatrixUtils.createRealMatrix(leftMatrixCoefficients).multiply(inverted);
  498.         // pack into array with original gradient dimension
  499.         final int gradientDimension = increasedGradientDimension - 1;
  500.         final GradientField field = GradientField.getField(gradientDimension);
  501.         final Gradient[] outputState = MathArrays.buildArray(field, state.length);
  502.         for (int i = 0; i < outputState.length; i++) {
  503.             final double[] gradient = new double[gradientDimension];
  504.             System.arraycopy(product.getRow(i), 0, gradient, 0, gradientDimension);
  505.             outputState[i] = new Gradient(state[i].getValue(), gradient);
  506.         }
  507.         return outputState;
  508.     }

  509. }