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.adjoint.cost;
18  
19  import org.hipparchus.Field;
20  import org.hipparchus.analysis.differentiation.Gradient;
21  import org.hipparchus.analysis.differentiation.GradientField;
22  import org.hipparchus.complex.Complex;
23  import org.hipparchus.complex.ComplexField;
24  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
25  import org.hipparchus.geometry.euclidean.threed.Vector3D;
26  import org.hipparchus.ode.events.Action;
27  import org.hipparchus.util.MathArrays;
28  import org.junit.jupiter.api.Assertions;
29  import org.junit.jupiter.api.Test;
30  import org.junit.jupiter.params.ParameterizedTest;
31  import org.junit.jupiter.params.provider.ValueSource;
32  import org.orekit.control.indirect.adjoint.CartesianAdjointDerivativesProvider;
33  import org.orekit.control.indirect.adjoint.FieldCartesianAdjointDerivativesProvider;
34  import org.orekit.frames.FramesFactory;
35  import org.orekit.orbits.CartesianOrbit;
36  import org.orekit.orbits.Orbit;
37  import org.orekit.propagation.FieldSpacecraftState;
38  import org.orekit.propagation.SpacecraftState;
39  import org.orekit.propagation.events.EventDetector;
40  import org.orekit.propagation.events.FieldEventDetector;
41  import org.orekit.propagation.integration.CombinedDerivatives;
42  import org.orekit.propagation.integration.FieldCombinedDerivatives;
43  import org.orekit.time.AbsoluteDate;
44  import org.orekit.utils.Constants;
45  import org.orekit.utils.PVCoordinates;
46  
47  import java.util.List;
48  import java.util.stream.Collectors;
49  import java.util.stream.Stream;
50  
51  class FieldUnboundedCartesianEnergyTest {
52  
53      @Test
54      void testGetFieldThrustAccelerationVectorFieldFactor() {
55          // GIVEN
56          final Complex massRateFactor = Complex.ONE;
57          final FieldUnboundedCartesianEnergy<Complex> unboundedCartesianEnergy = new FieldUnboundedCartesianEnergy<>("", massRateFactor);
58          final ComplexField field = ComplexField.getInstance();
59          final Complex[] fieldAdjoint = MathArrays.buildArray(field, 7);
60          fieldAdjoint[3] = new Complex(1.0, 0.0);
61          fieldAdjoint[4] = new Complex(2.0, 0.0);
62          final Complex mass = new Complex(3., 0.);
63          // WHEN
64          final FieldVector3D<Complex> fieldThrustVector = unboundedCartesianEnergy.getFieldThrustAccelerationVector(fieldAdjoint, mass);
65          // THEN
66          final double[] adjoint = new double[7];
67          for (int i = 0; i < adjoint.length; i++) {
68              adjoint[i] = fieldAdjoint[i].getReal();
69          }
70          final Vector3D thrustVector = unboundedCartesianEnergy.toCartesianCost().getThrustAccelerationVector(adjoint, mass.getReal());
71          Assertions.assertEquals(thrustVector, fieldThrustVector.toVector3D());
72      }
73  
74      @ParameterizedTest
75      @ValueSource(doubles = {1e-1, 1e10})
76      void testFieldDerivatives(final double mass) {
77          // GIVEN
78          final String name = "a";
79          final FieldUnboundedCartesianEnergy<Gradient> energy = new FieldUnboundedCartesianEnergy<>(name, GradientField.getField(1).getZero());
80          final FieldCartesianAdjointDerivativesProvider<Gradient> fieldAdjointDerivativesProvider = new FieldCartesianAdjointDerivativesProvider<>(energy);
81          final Orbit orbit = new CartesianOrbit(new PVCoordinates(new Vector3D(7e6, 1e3, 0), new Vector3D(10., 7e3, -200)),
82                  FramesFactory.getGCRF(), AbsoluteDate.ARBITRARY_EPOCH, Constants.EGM96_EARTH_MU);
83          final SpacecraftState state = new SpacecraftState(orbit, mass).addAdditionalData(name, new double[] {1., 2., 3., 4., 5., 6.});
84          final FieldSpacecraftState<Gradient> fieldState = new FieldSpacecraftState<>(GradientField.getField(1), state);
85          // WHEN
86          final FieldCombinedDerivatives<Gradient>fieldCombinedDerivatives = fieldAdjointDerivativesProvider.combinedDerivatives(fieldState);
87          // THEN
88          final CartesianAdjointDerivativesProvider adjointDerivativesProvider = new CartesianAdjointDerivativesProvider(energy.toCartesianCost());
89          final CombinedDerivatives combinedDerivatives = adjointDerivativesProvider.combinedDerivatives(state);
90          for (int i = 0; i < 7; i++) {
91              Assertions.assertEquals(combinedDerivatives.getMainStateDerivativesIncrements()[i],
92                      fieldCombinedDerivatives.getMainStateDerivativesIncrements()[i].getReal(), 1e-12);
93          }
94          for (int i = 0; i < 6; i++) {
95              Assertions.assertEquals(combinedDerivatives.getAdditionalDerivatives()[i],
96                      fieldCombinedDerivatives.getAdditionalDerivatives()[i].getReal(), 1e-10);
97          }
98      }
99  
100     @Test
101     void getFieldEventDetectorsSizeAndActionTest() {
102         // GIVEN
103         final Complex massFlowRateFactor = new Complex(2);
104         final FieldUnboundedCartesianEnergy<Complex> unboundedCartesianEnergy = new FieldUnboundedCartesianEnergy<>("", massFlowRateFactor);
105         final Field<Complex> field = ComplexField.getInstance();
106         // WHEN
107         final Stream<FieldEventDetector<Complex>> eventDetectorStream = unboundedCartesianEnergy.getFieldEventDetectors(field);
108         // THEN
109         final List<FieldEventDetector<Complex>> eventDetectors = eventDetectorStream.collect(Collectors.toList());
110         Assertions.assertEquals(1, eventDetectors.size());
111         Assertions.assertInstanceOf(FieldCartesianEnergyConsideringMass.FieldSingularityDetector.class, eventDetectors.get(0));
112         final FieldCartesianEnergyConsideringMass<Complex>.FieldSingularityDetector singularityDetector =
113                 (FieldCartesianEnergyConsideringMass<Complex>.FieldSingularityDetector) eventDetectors.get(0);
114         Assertions.assertEquals(Action.RESET_DERIVATIVES, singularityDetector.getHandler().eventOccurred(null, null, false));
115     }
116 
117     @Test
118     void getFieldEventDetectorsTest() {
119         // GIVEN
120         final Complex massFlowRateFactor = new Complex(3);
121         final String name = "a";
122         final FieldUnboundedCartesianEnergy<Complex> unboundedCartesianEnergy = new FieldUnboundedCartesianEnergy<>(name, massFlowRateFactor);
123         final Field<Complex> field = ComplexField.getInstance();
124         final Orbit orbit = new CartesianOrbit(new PVCoordinates(new Vector3D(7e6, 1e3, 0), new Vector3D(10., 7e3, -200)),
125                 FramesFactory.getGCRF(), AbsoluteDate.ARBITRARY_EPOCH, Constants.EGM96_EARTH_MU);
126         final SpacecraftState state = new SpacecraftState(orbit, 10.).addAdditionalData(name, new double[] {1., 2., 3., 4., 5., 6., 7.});
127         // WHEN
128         final Stream<FieldEventDetector<Complex>> fieldEventDetectorStream = unboundedCartesianEnergy.getFieldEventDetectors(field);
129         // THEN
130         final List<FieldEventDetector<Complex>> fieldEventDetectors = fieldEventDetectorStream.collect(Collectors.toList());
131         Assertions.assertEquals(1, fieldEventDetectors.size());
132         Assertions.assertInstanceOf(FieldCartesianEnergyConsideringMass.FieldSingularityDetector.class, fieldEventDetectors.get(0));
133         final FieldCartesianEnergyConsideringMass<Complex>.FieldSingularityDetector fieldSingularityDetector =
134                 (FieldCartesianEnergyConsideringMass<Complex>.FieldSingularityDetector) fieldEventDetectors.get(0);
135         final Complex gValue = fieldSingularityDetector.g(new FieldSpacecraftState<>(field, state));
136         final List<EventDetector> eventDetectors = unboundedCartesianEnergy.toCartesianCost().getEventDetectors().collect(Collectors.toList());
137         final CartesianEnergyConsideringMass.SingularityDetector singularityDetector =
138                 (CartesianEnergyConsideringMass.SingularityDetector) eventDetectors.get(0);
139         final double expectedG = singularityDetector.g(state);
140         Assertions.assertEquals(expectedG, gValue.getReal());
141     }
142 
143     @Test
144     void testToCartesianCost() {
145         // GIVEN
146         final Complex massRateFactor = Complex.ONE;
147         final FieldUnboundedCartesianEnergy<Complex> fieldCartesianEnergy = new FieldUnboundedCartesianEnergy<>("",
148                 massRateFactor);
149         // WHEN
150         final UnboundedCartesianEnergy cartesianEnergy = fieldCartesianEnergy.toCartesianCost();
151         // THEN
152         Assertions.assertEquals(cartesianEnergy.getAdjointName(), fieldCartesianEnergy.getAdjointName());
153     }
154 }