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  
19  import org.hipparchus.CalculusFieldElement;
20  import org.hipparchus.Field;
21  import org.hipparchus.analysis.differentiation.Gradient;
22  import org.hipparchus.analysis.differentiation.GradientField;
23  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
24  import org.hipparchus.linear.LUDecomposition;
25  import org.hipparchus.linear.MatrixUtils;
26  import org.hipparchus.linear.RealMatrix;
27  import org.hipparchus.ode.FieldOrdinaryDifferentialEquation;
28  import org.hipparchus.ode.nonstiff.FieldExplicitRungeKuttaIntegrator;
29  import org.hipparchus.util.FastMath;
30  import org.hipparchus.util.MathArrays;
31  import org.orekit.attitudes.FieldAttitude;
32  import org.orekit.control.indirect.adjoint.CartesianAdjointDerivativesProvider;
33  import org.orekit.control.indirect.adjoint.FieldCartesianAdjointDerivativesProvider;
34  import org.orekit.control.indirect.adjoint.cost.ControlSwitchDetector;
35  import org.orekit.control.indirect.adjoint.cost.FieldControlSwitchDetector;
36  import org.orekit.control.indirect.shooting.propagation.AdjointDynamicsProvider;
37  import org.orekit.control.indirect.shooting.propagation.ShootingPropagationSettings;
38  import org.orekit.forces.ForceModel;
39  import org.orekit.frames.Frame;
40  import org.orekit.orbits.FieldCartesianOrbit;
41  import org.orekit.propagation.FieldSpacecraftState;
42  import org.orekit.propagation.SpacecraftState;
43  import org.orekit.propagation.events.DateDetector;
44  import org.orekit.propagation.events.EventsLogger;
45  import org.orekit.propagation.events.FieldEventDetector;
46  import org.orekit.propagation.integration.FieldAdditionalDerivativesProvider;
47  import org.orekit.propagation.integration.FieldCombinedDerivatives;
48  import org.orekit.propagation.numerical.NumericalPropagator;
49  import org.orekit.propagation.sampling.PropagationStepRecorder;
50  import org.orekit.time.AbsoluteDate;
51  import org.orekit.time.FieldAbsoluteDate;
52  import org.orekit.utils.FieldAbsolutePVCoordinates;
53  import org.orekit.utils.FieldPVCoordinates;
54  
55  import java.util.Arrays;
56  import java.util.List;
57  import java.util.stream.Collectors;
58  
59  /**
60   * Abstract class for indirect single shooting methods with fixed initial Cartesian state.
61   * Inheritors must implement the iteration update, assuming derivatives are needed.
62   *
63   * @author Romain Serra
64   * @since 13.0
65   * @see CartesianAdjointDerivativesProvider
66   * @see org.orekit.control.indirect.adjoint.FieldCartesianAdjointDerivativesProvider
67   */
68  public abstract class AbstractFixedInitialCartesianSingleShooting extends AbstractIndirectShooting {
69  
70      /** Template for initial state. Contains the initial Cartesian coordinates. */
71      private final SpacecraftState initialSpacecraftStateTemplate;
72  
73      /**
74       * Step handler to record propagation steps.
75       */
76      private final PropagationStepRecorder propagationStepRecorder;
77  
78      /** Event logger. */
79      private final EventsLogger eventsLogger;
80  
81      /** Scales for automatic differentiation variables. */
82      private double[] scales;
83  
84      /**
85       * Constructor with boundary conditions as orbits.
86       * @param propagationSettings propagation settings
87       * @param initialSpacecraftStateTemplate template for initial propagation state
88       */
89      protected AbstractFixedInitialCartesianSingleShooting(final ShootingPropagationSettings propagationSettings,
90                                                            final SpacecraftState initialSpacecraftStateTemplate) {
91          super(propagationSettings);
92          this.initialSpacecraftStateTemplate = initialSpacecraftStateTemplate;
93          this.propagationStepRecorder = new PropagationStepRecorder();
94          this.eventsLogger = new EventsLogger();
95      }
96  
97      /**
98       * Build unit scales.
99       * @return scales
100      */
101     private double[] getUnitScales() {
102         final double[] unitScales = new double[getPropagationSettings().getAdjointDynamicsProvider().getDimension()];
103         Arrays.fill(unitScales, 1.);
104         return unitScales;
105     }
106 
107     /**
108      * Protected getter for the differentiation scales.
109      * @return scales for variable scales
110      */
111     protected double[] getScales() {
112         return scales;
113     }
114 
115     /**
116      * Returns the maximum number of iterations.
117      * @return maximum iterations
118      */
119     public abstract int getMaximumIterationCount();
120 
121     /** {@inheritDoc} */
122     @Override
123     public ShootingBoundaryOutput solve(final double initialMass, final double[] initialGuess) {
124         return solve(initialMass, initialGuess, getUnitScales());
125     }
126 
127     /**
128      * Solve for the boundary conditions, given an initial mass and an initial guess for the adjoint variables.
129      * Uses scales for automatic differentiation.
130      * @param initialMass initial mass
131      * @param initialGuess initial guess
132      * @param userScales scales
133      * @return boundary problem solution
134      */
135     public ShootingBoundaryOutput solve(final double initialMass, final double[] initialGuess,
136                                         final double[] userScales) {
137         scales = userScales.clone();
138         final SpacecraftState initialState = createStateWithMassAndAdjoint(initialMass, initialGuess);
139         final ShootingBoundaryOutput initialGuessSolution = computeCandidateSolution(initialState, 0);
140         if (initialGuessSolution.isConverged()) {
141             return initialGuessSolution;
142         } else {
143             return iterate(initialGuessSolution);
144         }
145     }
146 
147     /** {@inheritDoc} */
148     @Override
149     protected NumericalPropagator buildPropagator(final SpacecraftState initialState) {
150         final NumericalPropagator propagator = super.buildPropagator(initialState);
151         propagator.setStepHandler(propagationStepRecorder);
152         final CartesianAdjointDerivativesProvider derivativesProvider = (CartesianAdjointDerivativesProvider)
153             getPropagationSettings().getAdjointDynamicsProvider().buildAdditionalDerivativesProvider();
154         eventsLogger.clearLoggedEvents();
155         derivativesProvider.getCost().getEventDetectors()
156                 .forEach(detector -> propagator.addEventDetector(eventsLogger.monitorDetector(detector)));
157         return propagator;
158     }
159 
160     /**
161      * Form solution container with input initial state.
162      * @param initialState initial state
163      * @param iterationCount iteration count
164      * @return candidate solution
165      */
166     public abstract ShootingBoundaryOutput computeCandidateSolution(SpacecraftState initialState, int iterationCount);
167 
168     /**
169      * Iterate on initial guess to solve boundary problem.
170      * @param initialGuess initial guess
171      * @return solution (or null pointer if not converged)
172      */
173     private ShootingBoundaryOutput iterate(final ShootingBoundaryOutput initialGuess) {
174         int iterationCount = 1;
175         SpacecraftState initialState = initialGuess.getInitialState();
176         ShootingBoundaryOutput candidateSolution = initialGuess;
177         final String adjointName = getPropagationSettings().getAdjointDynamicsProvider().getAdjointName();
178         double[] initialAdjoint = initialState.getAdditionalState(adjointName).clone();
179         final double initialMass = initialState.getMass();
180         while (iterationCount < getMaximumIterationCount()) {
181             if (candidateSolution.isConverged()) {
182                 return candidateSolution;
183             }
184             final FieldSpacecraftState<Gradient> fieldInitialState = createFieldInitialStateWithMassAndAdjoint(initialMass,
185                 initialAdjoint);
186             initialState = fieldInitialState.toSpacecraftState();
187             final FieldSpacecraftState<Gradient> fieldTerminalState = propagateField(fieldInitialState);
188             initialAdjoint = updateShootingVariables(initialAdjoint, fieldTerminalState);
189             if (Double.isNaN(initialAdjoint[0])) {
190                 return candidateSolution;
191             } else {
192                 candidateSolution = computeCandidateSolution(initialState, iterationCount);
193             }
194             iterationCount++;
195         }
196         return candidateSolution;
197     }
198 
199     /**
200      * Propagate in Field. Does not use a full propagator (only the integrator, moving step by step using the history of non-Field propagation).
201      * It is faster as adaptive step and event detection are particularly slow with Field.
202      * @param fieldInitialState initial state
203      * @return terminal state
204      */
205     private FieldSpacecraftState<Gradient> propagateField(final FieldSpacecraftState<Gradient> fieldInitialState) {
206         final FieldAbsoluteDate<Gradient> initialDate = fieldInitialState.getDate();
207         final FieldExplicitRungeKuttaIntegrator<Gradient> fieldIntegrator = buildFieldIntegrator(fieldInitialState);
208         final FieldOrdinaryDifferentialEquation<Gradient> fieldODE = buildFieldODE(fieldInitialState.getDate());
209         final AdjointDynamicsProvider dynamicsProvider = getPropagationSettings().getAdjointDynamicsProvider();
210         AbsoluteDate date = initialDate.toAbsoluteDate();
211         Gradient[] integrationState = formatToArray(fieldInitialState, dynamicsProvider.getAdjointName());
212         // step-by-step integration
213         final List<EventsLogger.LoggedEvent> loggedEvents = eventsLogger.getLoggedEvents();
214         final List<AbsoluteDate> stepDates = propagationStepRecorder.copyStates().stream().map(SpacecraftState::getDate)
215                 .collect(Collectors.toList());
216         for (final AbsoluteDate stepDate: stepDates) {
217             final Gradient time = initialDate.durationFrom(date).negate();
218             final Gradient nextTime = initialDate.durationFrom(stepDate).negate();
219             integrationState = fieldIntegrator.singleStep(fieldODE, time, integrationState, nextTime);
220             // deal with switch event if any
221             if (!loggedEvents.isEmpty()) {
222                 for (final EventsLogger.LoggedEvent loggedEvent: loggedEvents) {
223                     final SpacecraftState loggedState = loggedEvent.getState();
224                     if (loggedEvent.getEventDetector() instanceof ControlSwitchDetector && loggedState.getDate().isEqualTo(stepDate)) {
225                         final ControlSwitchDetector switchDetector = (ControlSwitchDetector) loggedEvent.getEventDetector();
226                         integrationState = findSwitchEventAndUpdateState(date, integrationState,
227                                 initialDate.toAbsoluteDate(), switchDetector, dynamicsProvider);
228                     }
229                 }
230             }
231             date = new AbsoluteDate(stepDate);
232         }
233         return formatFromArray(date, integrationState);
234     }
235 
236     /**
237      * Create initial state with input mass.
238      * @param initialMass initial mass
239      * @return state
240      */
241     protected SpacecraftState createInitialStateWithMass(final double initialMass) {
242         return initialSpacecraftStateTemplate.withMass(initialMass);
243     }
244 
245     /**
246      * Create initial state with input mass and adjoint vector.
247      * @param initialMass initial mass
248      * @param initialAdjoint initial adjoint variables
249      * @return state
250      */
251     private SpacecraftState createStateWithMassAndAdjoint(final double initialMass, final double[] initialAdjoint) {
252         final String adjointName = getPropagationSettings().getAdjointDynamicsProvider().getAdjointName();
253         return createInitialStateWithMass(initialMass).addAdditionalData(adjointName, initialAdjoint);
254     }
255 
256     /**
257      * Create initial Gradient state with input mass and adjoint vector, the latter being the independent variables.
258      * @param initialMass initial mass
259      * @param initialAdjoint initial adjoint variables
260      * @return state
261      */
262     protected FieldSpacecraftState<Gradient> createFieldInitialStateWithMassAndAdjoint(final double initialMass,
263                                                                                        final double[] initialAdjoint) {
264         final int parametersNumber = initialAdjoint.length;
265         final GradientField field = GradientField.getField(parametersNumber);
266         final FieldSpacecraftState<Gradient> stateWithoutAdjoint = new FieldSpacecraftState<>(field,
267                 createInitialStateWithMass(initialMass));
268         final Gradient[] fieldInitialAdjoint = MathArrays.buildArray(field, initialAdjoint.length);
269         for (int i = 0; i < parametersNumber; i++) {
270             fieldInitialAdjoint[i] = Gradient.variable(parametersNumber, i, 0.).multiply(getScales()[i]).add(initialAdjoint[i]);
271         }
272         final String adjointName = getPropagationSettings().getAdjointDynamicsProvider().getAdjointName();
273         return stateWithoutAdjoint.addAdditionalData(adjointName, fieldInitialAdjoint);
274     }
275 
276     /**
277      * Create state.
278      * @param date epoch
279      * @param cartesian Cartesian variables
280      * @param mass mass
281      * @param adjoint adjoint variables
282      * @param <T> field type
283      * @return state
284      */
285     protected <T extends CalculusFieldElement<T>> FieldSpacecraftState<T> createFieldState(final FieldAbsoluteDate<T> date,
286                                                                                            final T[] cartesian,
287                                                                                            final T mass,
288                                                                                            final T[] adjoint) {
289         final Field<T> field = mass.getField();
290         final Frame frame = getPropagationSettings().getPropagationFrame();
291         final FieldVector3D<T> position = new FieldVector3D<>(Arrays.copyOfRange(cartesian, 0, 3));
292         final FieldVector3D<T> velocity = new FieldVector3D<>(Arrays.copyOfRange(cartesian, 3, 6));
293         final FieldPVCoordinates<T> pvCoordinates = new FieldPVCoordinates<>(position, velocity);
294         final FieldSpacecraftState<T> stateWithoutAdjoint;
295         if (initialSpacecraftStateTemplate.isOrbitDefined()) {
296             final T mu = field.getZero().newInstance(initialSpacecraftStateTemplate.getOrbit().getMu());
297             final FieldCartesianOrbit<T> orbit = new FieldCartesianOrbit<>(pvCoordinates, frame, date, mu);
298             final FieldAttitude<T> attitude = getPropagationSettings().getAttitudeProvider().getAttitude(orbit,
299                     orbit.getDate(), orbit.getFrame());
300             stateWithoutAdjoint = new FieldSpacecraftState<>(orbit, attitude).withMass(mass);
301         } else {
302             final FieldAbsolutePVCoordinates<T> absolutePVCoordinates = new FieldAbsolutePVCoordinates<>(frame, date,
303                     pvCoordinates);
304             final FieldAttitude<T> attitude = getPropagationSettings().getAttitudeProvider().getAttitude(absolutePVCoordinates,
305                     absolutePVCoordinates.getDate(), absolutePVCoordinates.getFrame());
306             stateWithoutAdjoint = new FieldSpacecraftState<>(absolutePVCoordinates, attitude).withMass(mass);
307         }
308         final String adjointName = getPropagationSettings().getAdjointDynamicsProvider().getAdjointName();
309         return stateWithoutAdjoint.addAdditionalData(adjointName, adjoint);
310     }
311 
312     /**
313      * Update shooting variables.
314      * @param originalShootingVariables original shooting variables (before update)
315      * @param fieldTerminalState final state of gradient propagation
316      * @return updated shooting variables
317      */
318     protected abstract double[] updateShootingVariables(double[] originalShootingVariables,
319                                                         FieldSpacecraftState<Gradient> fieldTerminalState);
320 
321     /**
322      * Build Ordinary Differential Equation for Field.
323      * @param referenceDate date defining the origin of times
324      * @param <T> field type
325      * @return Field ODE
326      * @since 13.0
327      */
328     protected <T extends CalculusFieldElement<T>> FieldOrdinaryDifferentialEquation<T> buildFieldODE(final FieldAbsoluteDate<T> referenceDate) {
329         final Field<T> field = referenceDate.getField();
330         final AdjointDynamicsProvider adjointDynamicsProvider = getPropagationSettings().getAdjointDynamicsProvider();
331         final FieldAdditionalDerivativesProvider<T> derivativesProvider = adjointDynamicsProvider
332                 .buildFieldAdditionalDerivativesProvider(field);
333 
334         return new FieldOrdinaryDifferentialEquation<T>() {
335             @Override
336             public int getDimension() {
337                 return 7 + adjointDynamicsProvider.getDimension();
338             }
339 
340             @Override
341             public T[] computeDerivatives(final T t, final T[] y) {
342                 // build state
343                 final T[] cartesian = Arrays.copyOfRange(y, 0, 6);
344                 final FieldAbsoluteDate<T> date = referenceDate.shiftedBy(t);
345                 final Field<T> field = date.getField();
346                 final T zero = field.getZero();
347                 final T[] adjoint = Arrays.copyOfRange(y, 7, y.length);
348                 final FieldSpacecraftState<T> state = createFieldState(date, cartesian, y[6], adjoint);
349                 // compute derivatives
350                 final T[] yDot = MathArrays.buildArray(field, getDimension());
351                 yDot[0] = zero.add(y[3]);
352                 yDot[1] = zero.add(y[4]);
353                 yDot[2] = zero.add(y[5]);
354                 FieldVector3D<T> totalAcceleration = FieldVector3D.getZero(field);
355                 for (final ForceModel forceModel: getPropagationSettings().getForceModels()) {
356                     final FieldVector3D<T> acceleration = forceModel.acceleration(state, forceModel.getParameters(field));
357                     totalAcceleration = totalAcceleration.add(acceleration);
358                 }
359                 yDot[3] = totalAcceleration.getX();
360                 yDot[4] = totalAcceleration.getY();
361                 yDot[5] = totalAcceleration.getZ();
362                 final FieldCombinedDerivatives<T> combinedDerivatives = derivativesProvider.combinedDerivatives(state);
363                 final T[] cartesianDotContribution = combinedDerivatives.getMainStateDerivativesIncrements();
364                 for (int i = 0; i < cartesianDotContribution.length; i++) {
365                     yDot[i] = yDot[i].add(cartesianDotContribution[i]);
366                 }
367                 final T[] adjointDot = combinedDerivatives.getAdditionalDerivatives();
368                 System.arraycopy(adjointDot, 0, yDot, yDot.length - adjointDot.length, adjointDot.length);
369                 return yDot;
370             }
371         };
372     }
373 
374     /**
375      * Form array from Orekit object.
376      * @param fieldState state
377      * @param adjointName adjoint name
378      * @return propagation state as array
379      */
380     private Gradient[] formatToArray(final FieldSpacecraftState<Gradient> fieldState,
381                                      final String adjointName) {
382         final Gradient[] adjoint = fieldState.getAdditionalState(adjointName);
383         final int adjointDimension = adjoint.length;
384         final Gradient[] integrationState = MathArrays.buildArray(fieldState.getMass().getField(), 7 + adjointDimension);
385         final FieldPVCoordinates<Gradient> pvCoordinates = fieldState.getPVCoordinates();
386         System.arraycopy(pvCoordinates.getPosition().toArray(), 0, integrationState, 0, 3);
387         System.arraycopy(pvCoordinates.getVelocity().toArray(), 0, integrationState, 3, 3);
388         integrationState[6] = fieldState.getMass();
389         System.arraycopy(adjoint, 0, integrationState, 7, adjointDimension);
390         return integrationState;
391     }
392 
393     /**
394      * Form Orekit object from array.
395      * @param date date
396      * @param integrationState propagation state as array
397      * @return Orekit state
398      */
399     private FieldSpacecraftState<Gradient> formatFromArray(final AbsoluteDate date, final Gradient[] integrationState) {
400         final Gradient[] terminalCartesian = Arrays.copyOfRange(integrationState, 0, 6);
401         final Gradient[] terminalAdjoint = Arrays.copyOfRange(integrationState, 7, integrationState.length);
402         final Field<Gradient> field = terminalCartesian[0].getField();
403         return createFieldState(new FieldAbsoluteDate<>(field, date), terminalCartesian, integrationState[6],
404                 terminalAdjoint);
405     }
406 
407     /**
408      * Iterate over field switch detectors and find the one that was triggered in the non-Field propagation.
409      * Then, use it to update the gradient.
410      * @param date date
411      * @param integrationState integration variables
412      * @param referenceDate date at start of propagation
413      * @param switchDetector switch detector
414      * @param dynamicsProvider adjoint dynamics provider
415      * @return update integration state
416      */
417     private Gradient[] findSwitchEventAndUpdateState(final AbsoluteDate date, final Gradient[] integrationState,
418                                                      final AbsoluteDate referenceDate,
419                                                      final ControlSwitchDetector switchDetector,
420                                                      final AdjointDynamicsProvider dynamicsProvider) {
421         final int shootingVariables = dynamicsProvider.getDimension();
422         final int increasedVariables = shootingVariables + 1;
423         final GradientField increasedVariablesField = GradientField.getField(increasedVariables);
424         final FieldCartesianAdjointDerivativesProvider<Gradient> fieldDerivativesProvider =
425                 (FieldCartesianAdjointDerivativesProvider<Gradient>) dynamicsProvider.buildFieldAdditionalDerivativesProvider(increasedVariablesField);
426         final List<FieldEventDetector<Gradient>> fieldEventDetectors = fieldDerivativesProvider.getCost()
427                 .getFieldEventDetectors(increasedVariablesField).collect(Collectors.toList());
428         final FieldSpacecraftState<Gradient> fieldState = formatFromArray(date, integrationState);
429         final double expectedG = switchDetector.g(fieldState.toSpacecraftState());
430         for (final FieldEventDetector<Gradient> fieldEventDetector : fieldEventDetectors) {
431             if (fieldEventDetector instanceof FieldControlSwitchDetector) {
432                 final double actualG = fieldEventDetector.g(fieldState).getReal();
433                 if (FastMath.abs(actualG - expectedG) < 1e-10) {
434                     return updateStateWithTaylorMapInversion(date, integrationState, referenceDate,
435                             fieldEventDetector, dynamicsProvider.getAdjointName());
436                 }
437             }
438         }
439         return integrationState;
440     }
441 
442     /**
443      * Update the differential information by taking into account that a state-dependent event was triggered.
444      * @param date date
445      * @param integrationState integration variables
446      * @param initialDate date at start of propagation
447      * @param fieldDetector switch detector
448      * @param adjointName adjoint name
449      * @return updated integration state
450      */
451     private Gradient[] updateStateWithTaylorMapInversion(final AbsoluteDate date, final Gradient[] integrationState,
452                                                          final AbsoluteDate initialDate,
453                                                          final FieldEventDetector<Gradient> fieldDetector,
454                                                          final String adjointName) {
455         // form array with increased gradient size
456         final Gradient threshold = fieldDetector.getThreshold();
457         final int increasedVariables = threshold.getFreeParameters();
458         final GradientField increasedVariablesField = GradientField.getField(increasedVariables);
459         final Gradient[] increasedVariablesArray = MathArrays.buildArray(increasedVariablesField,
460                 integrationState.length);
461         for (int i = 0; i < integrationState.length; i++) {
462             increasedVariablesArray[i] = integrationState[i].stackVariable();
463         }
464         final Gradient dt = Gradient.variable(increasedVariables, increasedVariables - 1, 1);
465         // add event function as new variable and nullify it before switch
466         final Gradient gBefore = evaluateG(date, increasedVariablesArray, initialDate, fieldDetector, dt, adjointName);
467         final Gradient[] mapBeforeSwitch = invertTaylorMap(increasedVariablesArray, gBefore);
468         // re-increase gradient size and slightly shift in time to pass switch
469         for (int i = 0; i < integrationState.length; i++) {
470             increasedVariablesArray[i] = mapBeforeSwitch[i].stackVariable();
471         }
472         final boolean isForward = date.isAfter(initialDate);
473         final double tinyStep = FastMath.min(DateDetector.DEFAULT_THRESHOLD, threshold.getValue());
474         final double timeShift = isForward ? tinyStep : -tinyStep;
475         final FieldSpacecraftState<Gradient> stateAfterSwitch = formatFromArray(date, increasedVariablesArray)
476                 .shiftedBy(timeShift);
477         final Gradient[] stateAfterSwitchArray = formatToArray(stateAfterSwitch, adjointName);
478         // add event function as new variable and nullify it after switch
479         final AbsoluteDate shiftedDate = date.shiftedBy(timeShift);
480         final Gradient gAfter = evaluateG(shiftedDate, stateAfterSwitchArray, initialDate, fieldDetector, dt.negate(),
481                 adjointName);
482         return invertTaylorMap(increasedVariablesArray, gAfter);
483     }
484 
485     /**
486      * Evaluate event function in proper Taylor algebra.
487      * @param date date
488      * @param increasedVariablesArray integration variables with increased gradient size
489      * @param initialDate date at start of propagation
490      * @param fieldDetector switch detector
491      * @param dt time as independent variable
492      * @param adjointName adjoint name
493      * @return g
494      */
495     private Gradient evaluateG(final AbsoluteDate date, final Gradient[] increasedVariablesArray,
496                                final AbsoluteDate initialDate, final FieldEventDetector<Gradient> fieldDetector,
497                                final Gradient dt, final String adjointName) {
498         final Field<Gradient> field = increasedVariablesArray[0].getField();
499         final FieldAbsoluteDate<Gradient> referenceDate = new FieldAbsoluteDate<>(field, initialDate);
500         final FieldOrdinaryDifferentialEquation<Gradient> fieldODE = buildFieldODE(referenceDate);
501         final double duration = date.durationFrom(initialDate);
502         final Gradient[] derivatives = fieldODE.computeDerivatives(field.getZero().newInstance(duration),
503                 increasedVariablesArray);
504         final FieldSpacecraftState<Gradient> fieldState = formatFromArray(date, increasedVariablesArray);
505         final FieldSpacecraftState<Gradient> fieldStateWithAdjointDerivative = fieldState.addAdditionalStateDerivative(adjointName,
506                 Arrays.copyOfRange(derivatives, derivatives.length - 7, derivatives.length));
507         return fieldDetector.g(fieldStateWithAdjointDerivative.shiftedBy(dt));
508     }
509 
510     /**
511      * Invert so-called Taylor map to make the event function value an independent variable.
512      * Then nullify its variation to keep only the derivatives of interest.
513      * @param state integration variables with dt as last gradient variable
514      * @param g event function evaluated with dt as last gradient variable
515      * @return update integration variables
516      */
517     private static Gradient[] invertTaylorMap(final Gradient[] state, final Gradient g) {
518         // swap dt and g as variables of algebra
519         final int increasedGradientDimension = g.getFreeParameters();
520         final RealMatrix rightMatrix = MatrixUtils.createRealIdentityMatrix(increasedGradientDimension);
521         rightMatrix.setRow(rightMatrix.getRowDimension() - 1, g.getGradient());
522         final LUDecomposition luDecomposition = new LUDecomposition(rightMatrix, 0.);
523         final RealMatrix inverted = luDecomposition.getSolver().getInverse();
524         final double[][] leftMatrixCoefficients = new double[state.length][];
525         for (int i = 0; i < state.length; i++) {
526             leftMatrixCoefficients[i] = state[i].getGradient();
527         }
528         final RealMatrix product = MatrixUtils.createRealMatrix(leftMatrixCoefficients).multiply(inverted);
529         // pack into array with original gradient dimension
530         final int gradientDimension = increasedGradientDimension - 1;
531         final GradientField field = GradientField.getField(gradientDimension);
532         final Gradient[] outputState = MathArrays.buildArray(field, state.length);
533         for (int i = 0; i < outputState.length; i++) {
534             final double[] gradient = new double[gradientDimension];
535             System.arraycopy(product.getRow(i), 0, gradient, 0, gradientDimension);
536             outputState[i] = new Gradient(state[i].getValue(), gradient);
537         }
538         return outputState;
539     }
540 
541 }