AbstractFixedBoundaryCartesianSingleShooting.java

  1. /* Copyright 2022-2024 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.Field;
  19. import org.hipparchus.analysis.differentiation.Gradient;
  20. import org.hipparchus.analysis.differentiation.GradientField;
  21. import org.hipparchus.util.FastMath;
  22. import org.hipparchus.util.MathArrays;
  23. import org.orekit.attitudes.Attitude;
  24. import org.orekit.control.indirect.adjoint.CartesianAdjointDerivativesProvider;
  25. import org.orekit.control.indirect.adjoint.FieldCartesianAdjointDerivativesProvider;
  26. import org.orekit.control.indirect.shooting.boundary.CartesianBoundaryConditionChecker;
  27. import org.orekit.control.indirect.shooting.boundary.FixedTimeBoundaryOrbits;
  28. import org.orekit.control.indirect.shooting.boundary.FixedTimeCartesianBoundaryStates;
  29. import org.orekit.control.indirect.shooting.propagation.ShootingPropagationSettings;
  30. import org.orekit.frames.Frame;
  31. import org.orekit.orbits.CartesianOrbit;
  32. import org.orekit.orbits.Orbit;
  33. import org.orekit.propagation.FieldSpacecraftState;
  34. import org.orekit.propagation.SpacecraftState;
  35. import org.orekit.propagation.numerical.FieldNumericalPropagator;
  36. import org.orekit.propagation.numerical.NumericalPropagator;
  37. import org.orekit.time.FieldAbsoluteDate;
  38. import org.orekit.utils.AbsolutePVCoordinates;
  39. import org.orekit.utils.TimeStampedPVCoordinates;

  40. /**
  41.  * Abstract class for indirect single shooting methods with Cartesian coordinates for fixed time fixed boundary.
  42.  * Inheritors must implement the iteration update, assuming derivatives are needed.
  43.  * Terminal mass is assumed to be free, thus corresponding adjoint must vanish at terminal time.
  44.  * On the other hand, other terminal adjoint variables are free because the Cartesian state is fixed.
  45.  *
  46.  * @author Romain Serra
  47.  * @since 12.2
  48.  * @see org.orekit.control.indirect.adjoint.CartesianAdjointDerivativesProvider
  49.  * @see org.orekit.control.indirect.adjoint.FieldCartesianAdjointDerivativesProvider
  50.  */
  51. public abstract class AbstractFixedBoundaryCartesianSingleShooting extends AbstractIndirectShooting {

  52.     /** Default value for defects scaling. */
  53.     private static final double DEFAULT_SCALE = 1.;

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

  56.     /** Terminal Cartesian coordinates. */
  57.     private final TimeStampedPVCoordinates terminalCartesianState;

  58.     /** Condition checker. */
  59.     private final CartesianBoundaryConditionChecker conditionChecker;

  60.     /** Scale for velocity defects (m). */
  61.     private double scaleVelocityDefects;

  62.     /** Scale for position defects (m/s). */
  63.     private double scalePositionDefects;

  64.     /** Tolerance for convergence on terminal mass adjoint, if applicable to dynamics. */
  65.     private double toleranceMassAdjoint = DEFAULT_TOLERANCE_MASS_ADJOINT;

  66.     /**
  67.      * Constructor with boundary conditions as orbits.
  68.      * @param propagationSettings propagation settings
  69.      * @param boundaryConditions boundary conditions as {@link FixedTimeCartesianBoundaryStates}
  70.      * @param conditionChecker boundary condition checker
  71.      */
  72.     protected AbstractFixedBoundaryCartesianSingleShooting(final ShootingPropagationSettings propagationSettings,
  73.                                                            final FixedTimeCartesianBoundaryStates boundaryConditions,
  74.                                                            final CartesianBoundaryConditionChecker conditionChecker) {
  75.         super(propagationSettings);
  76.         this.conditionChecker = conditionChecker;
  77.         this.initialSpacecraftStateTemplate = buildInitialStateTemplate(boundaryConditions.getInitialCartesianState());
  78.         this.terminalCartesianState = boundaryConditions.getTerminalCartesianState().getPVCoordinates(propagationSettings.getPropagationFrame());
  79.         this.scalePositionDefects = DEFAULT_SCALE;
  80.         this.scaleVelocityDefects = DEFAULT_SCALE;
  81.     }

  82.     /**
  83.      * Constructor with boundary conditions as orbits.
  84.      * @param propagationSettings propagation settings
  85.      * @param boundaryConditions boundary conditions as {@link FixedTimeBoundaryOrbits}
  86.      * @param conditionChecker boundary condition checker
  87.      */
  88.     protected AbstractFixedBoundaryCartesianSingleShooting(final ShootingPropagationSettings propagationSettings,
  89.                                                            final FixedTimeBoundaryOrbits boundaryConditions,
  90.                                                            final CartesianBoundaryConditionChecker conditionChecker) {
  91.         super(propagationSettings);
  92.         this.conditionChecker = conditionChecker;
  93.         this.initialSpacecraftStateTemplate = buildInitialStateTemplate(boundaryConditions.getInitialOrbit());
  94.         this.terminalCartesianState = boundaryConditions.getTerminalOrbit().getPVCoordinates(propagationSettings.getPropagationFrame());
  95.         this.scalePositionDefects = DEFAULT_SCALE;
  96.         this.scaleVelocityDefects = DEFAULT_SCALE;
  97.     }

  98.     /**
  99.      * Setter for scale of position defects.
  100.      * @param scalePositionDefects new scale
  101.      */
  102.     public void setScalePositionDefects(final double scalePositionDefects) {
  103.         this.scalePositionDefects = scalePositionDefects;
  104.     }

  105.     /**
  106.      * Getter for scale of position defects.
  107.      * @return scale
  108.      */
  109.     public double getScalePositionDefects() {
  110.         return scalePositionDefects;
  111.     }

  112.     /**
  113.      * Setter for scale of velocity defects.
  114.      * @param scaleVelocityDefects new scale
  115.      */
  116.     public void setScaleVelocityDefects(final double scaleVelocityDefects) {
  117.         this.scaleVelocityDefects = scaleVelocityDefects;
  118.     }

  119.     /**
  120.      * Getter for scale of velocity defects.
  121.      * @return scale
  122.      */
  123.     public double getScaleVelocityDefects() {
  124.         return scaleVelocityDefects;
  125.     }

  126.     /**
  127.      * Getter for the boundary condition checker.
  128.      * @return checker
  129.      */
  130.     protected CartesianBoundaryConditionChecker getConditionChecker() {
  131.         return conditionChecker;
  132.     }

  133.     /**
  134.      * Getter for the target terminal Cartesian state vector.
  135.      * @return expected terminal state
  136.      */
  137.     protected TimeStampedPVCoordinates getTerminalCartesianState() {
  138.         return terminalCartesianState;
  139.     }

  140.     /**
  141.      * Setter for mass adjoint tolerance.
  142.      * @param toleranceMassAdjoint new tolerance value
  143.      */
  144.     public void setToleranceMassAdjoint(final double toleranceMassAdjoint) {
  145.         this.toleranceMassAdjoint = FastMath.abs(toleranceMassAdjoint);
  146.     }

  147.     /**
  148.      * Create template initial state (without adjoint varialbles) for propagation from orbits.
  149.      * @param initialOrbit initial orbit
  150.      * @return template propagation state
  151.      */
  152.     private SpacecraftState buildInitialStateTemplate(final Orbit initialOrbit) {
  153.         final Frame frame = getPropagationSettings().getPropagationFrame();
  154.         final CartesianOrbit cartesianOrbit = new CartesianOrbit(initialOrbit.getPVCoordinates(frame), frame,
  155.             initialOrbit.getDate(), initialOrbit.getMu());
  156.         final Attitude attitude = getPropagationSettings().getAttitudeProvider()
  157.                 .getAttitude(cartesianOrbit, cartesianOrbit.getDate(), cartesianOrbit.getFrame());
  158.         return new SpacecraftState(cartesianOrbit, attitude);
  159.     }

  160.     /**
  161.      * Create template initial state (without adjoint varialbles) for propagation.
  162.      * @param initialCartesianState initial Cartesian state
  163.      * @return template propagation state
  164.      */
  165.     private SpacecraftState buildInitialStateTemplate(final AbsolutePVCoordinates initialCartesianState) {
  166.         final Frame frame = getPropagationSettings().getPropagationFrame();
  167.         final AbsolutePVCoordinates absolutePVCoordinates = new AbsolutePVCoordinates(frame,
  168.                 initialCartesianState.getPVCoordinates(frame));
  169.         final Attitude attitude = getPropagationSettings().getAttitudeProvider()
  170.                 .getAttitude(absolutePVCoordinates, absolutePVCoordinates.getDate(), absolutePVCoordinates.getFrame());
  171.         return new SpacecraftState(absolutePVCoordinates, attitude);
  172.     }

  173.     /** {@inheritDoc} */
  174.     @Override
  175.     public ShootingBoundaryOutput solve(final double initialMass, final double[] initialGuess) {
  176.         // check initial guess
  177.         final SpacecraftState initialState = createStateWithMassAndAdjoint(initialMass, initialGuess);
  178.         final ShootingBoundaryOutput initialGuessSolution = computeCandidateSolution(initialState, 0);
  179.         if (initialGuessSolution.isConverged()) {
  180.             return initialGuessSolution;
  181.         } else {
  182.             return iterate(initialMass, initialGuess);
  183.         }
  184.     }

  185.     /** {@inheritDoc} */
  186.     @Override
  187.     protected NumericalPropagator buildPropagator(final SpacecraftState initialState) {
  188.         final NumericalPropagator propagator = super.buildPropagator(initialState);
  189.         final CartesianAdjointDerivativesProvider derivativesProvider = (CartesianAdjointDerivativesProvider)
  190.             getPropagationSettings().getAdjointDynamicsProvider().buildAdditionalDerivativesProvider();
  191.         derivativesProvider.getCost().getEventDetectors().forEach(propagator::addEventDetector);
  192.         return propagator;
  193.     }

  194.     /** {@inheritDoc} */
  195.     @Override
  196.     protected FieldNumericalPropagator<Gradient> buildFieldPropagator(final FieldSpacecraftState<Gradient> initialState) {
  197.         final FieldNumericalPropagator<Gradient> fieldPropagator = super.buildFieldPropagator(initialState);
  198.         final Field<Gradient> field = fieldPropagator.getField();
  199.         final FieldCartesianAdjointDerivativesProvider<Gradient> derivativesProvider = (FieldCartesianAdjointDerivativesProvider<Gradient>)
  200.             getPropagationSettings().getAdjointDynamicsProvider().buildFieldAdditionalDerivativesProvider(field);
  201.         derivativesProvider.getCost().getFieldEventDetectors(field).forEach(fieldPropagator::addEventDetector);
  202.         return fieldPropagator;
  203.     }

  204.     /**
  205.      * Form solution container with input initial state.
  206.      * @param initialState initial state
  207.      * @param iterationCount iteration count
  208.      * @return candidate solution
  209.      */
  210.     private ShootingBoundaryOutput computeCandidateSolution(final SpacecraftState initialState,
  211.                                                             final int iterationCount) {
  212.         final NumericalPropagator propagator = buildPropagator(initialState);
  213.         final SpacecraftState actualTerminalState = propagator.propagate(getTerminalCartesianState().getDate());
  214.         final boolean converged = checkConvergence(actualTerminalState);
  215.         return new ShootingBoundaryOutput(converged, iterationCount, initialState, getPropagationSettings(), actualTerminalState);
  216.     }

  217.     /**
  218.      * Iterate on initial guess to solve boundary problem.
  219.      * @param initialMass initial mass
  220.      * @param initialGuess initial guess for initial adjoint variables
  221.      * @return solution (or null pointer if not converged)
  222.      */
  223.     private ShootingBoundaryOutput iterate(final double initialMass, final double[] initialGuess) {
  224.         double[] initialAdjoint = initialGuess.clone();
  225.         int iterationCount = 0;
  226.         int maximumIterationCount = getConditionChecker().getMaximumIterationCount();
  227.         SpacecraftState initialState = createStateWithMassAndAdjoint(initialMass, initialGuess);
  228.         while (iterationCount < maximumIterationCount) {
  229.             final FieldSpacecraftState<Gradient> fieldInitialState = createFieldStateWithMassAndAdjoint(initialMass,
  230.                 initialAdjoint);
  231.             final Field<Gradient> field = fieldInitialState.getDate().getField();
  232.             final FieldAbsoluteDate<Gradient> fieldTerminalDate = new FieldAbsoluteDate<>(field, getTerminalCartesianState().getDate());
  233.             final FieldNumericalPropagator<Gradient> fieldPropagator = buildFieldPropagator(fieldInitialState);
  234.             final FieldSpacecraftState<Gradient> fieldTerminalState = fieldPropagator.propagate(fieldTerminalDate);
  235.             initialState = fieldInitialState.toSpacecraftState();
  236.             if (checkConvergence(fieldTerminalState.toSpacecraftState())) {
  237.                 // make sure non-Field version also satisfies convergence criterion
  238.                 final ShootingBoundaryOutput solution = computeCandidateSolution(initialState, iterationCount);
  239.                 if (solution.isConverged()) {
  240.                     return solution;
  241.                 }
  242.             }
  243.             initialAdjoint = updateAdjoint(initialAdjoint, fieldTerminalState);
  244.             if (Double.isNaN(initialAdjoint[0])) {
  245.                 return computeCandidateSolution(initialState, iterationCount);
  246.             }
  247.             iterationCount++;
  248.             maximumIterationCount = getConditionChecker().getMaximumIterationCount();
  249.         }
  250.         return computeCandidateSolution(initialState, maximumIterationCount);
  251.     }

  252.     /**
  253.      * Checks convergence.
  254.      * @param actualTerminalState achieved terminal state
  255.      * @return convergence flag
  256.      */
  257.     private boolean checkConvergence(final SpacecraftState actualTerminalState) {
  258.         final boolean isCartesianConverged = getConditionChecker().isConverged(getTerminalCartesianState(),
  259.                 actualTerminalState.getPVCoordinates());
  260.         if (isCartesianConverged) {
  261.             final String adjointName = getPropagationSettings().getAdjointDynamicsProvider().getAdjointName();
  262.             final double[] terminalAdjoint = actualTerminalState.getAdditionalState(adjointName);
  263.             if (terminalAdjoint.length == 7) {
  264.                 return FastMath.abs(terminalAdjoint[6]) < toleranceMassAdjoint;
  265.             } else {
  266.                 return true;
  267.             }
  268.         } else {
  269.             return false;
  270.         }
  271.     }

  272.     /**
  273.      * Create initial state with input mass and adjoint vector.
  274.      * @param initialMass initial mass
  275.      * @param initialAdjoint initial adjoint variables
  276.      * @return state
  277.      */
  278.     protected SpacecraftState createStateWithMassAndAdjoint(final double initialMass, final double[] initialAdjoint) {
  279.         final String adjointName = getPropagationSettings().getAdjointDynamicsProvider().getAdjointName();
  280.         return createStateWithMass(initialMass).addAdditionalState(adjointName, initialAdjoint);
  281.     }

  282.     /**
  283.      * Create initial state with input mass.
  284.      * @param initialMass initial mass
  285.      * @return state
  286.      */
  287.     private SpacecraftState createStateWithMass(final double initialMass) {
  288.         if (initialSpacecraftStateTemplate.isOrbitDefined()) {
  289.             return new SpacecraftState(initialSpacecraftStateTemplate.getOrbit(),
  290.                 initialSpacecraftStateTemplate.getAttitude(), initialMass);
  291.         } else {
  292.             return new SpacecraftState(initialSpacecraftStateTemplate.getAbsPVA(),
  293.                 initialSpacecraftStateTemplate.getAttitude(), initialMass);
  294.         }
  295.     }

  296.     /**
  297.      * Create initial state with input mass and adjoint vector.
  298.      * @param initialMass initial mass
  299.      * @param initialAdjoint initial adjoint variables
  300.      * @return state
  301.      */
  302.     protected FieldSpacecraftState<Gradient> createFieldStateWithMassAndAdjoint(final double initialMass,
  303.                                                                                 final double[] initialAdjoint) {
  304.         final int parametersNumber = initialAdjoint.length;
  305.         final GradientField field = GradientField.getField(parametersNumber);
  306.         final FieldSpacecraftState<Gradient> stateWithoutAdjoint = new FieldSpacecraftState<>(field, createStateWithMass(initialMass));
  307.         final Gradient[] fieldInitialAdjoint = MathArrays.buildArray(field, initialAdjoint.length);
  308.         for (int i = 0; i < parametersNumber; i++) {
  309.             fieldInitialAdjoint[i] = Gradient.variable(parametersNumber, i, initialAdjoint[i]);
  310.         }
  311.         final String adjointName = getPropagationSettings().getAdjointDynamicsProvider().getAdjointName();
  312.         return stateWithoutAdjoint.addAdditionalState(adjointName, fieldInitialAdjoint);
  313.     }

  314.     /**
  315.      * Update initial adjoint vector.
  316.      * @param originalInitialAdjoint original initial adjoint (before update)
  317.      * @param fieldTerminalState final state of gradient propagation
  318.      * @return updated initial adjoint vector
  319.      */
  320.     protected abstract double[] updateAdjoint(double[] originalInitialAdjoint,
  321.                                               FieldSpacecraftState<Gradient> fieldTerminalState);
  322. }