1   /* Copyright 2002-2022 CS GROUP
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.estimation.measurements;
18  
19  import java.util.List;
20  
21  import org.hipparchus.geometry.euclidean.threed.Vector3D;
22  import org.hipparchus.stat.descriptive.StreamingStatistics;
23  import org.hipparchus.stat.descriptive.rank.Median;
24  import org.hipparchus.util.FastMath;
25  import org.junit.Assert;
26  import org.junit.Test;
27  import org.orekit.estimation.Context;
28  import org.orekit.estimation.EstimationTestUtils;
29  import org.orekit.orbits.OrbitType;
30  import org.orekit.orbits.PositionAngle;
31  import org.orekit.propagation.Propagator;
32  import org.orekit.propagation.SpacecraftState;
33  import org.orekit.propagation.conversion.NumericalPropagatorBuilder;
34  import org.orekit.time.AbsoluteDate;
35  import org.orekit.utils.Differentiation;
36  import org.orekit.utils.ParameterDriver;
37  import org.orekit.utils.ParameterFunction;
38  import org.orekit.utils.StateFunction;
39  
40  public class AngularRaDecTest {
41  
42      /** Test the values of radec measurements.
43       *  Added after bug 473 was reported by John Grimes.
44       */
45      @Test
46      public void testBug473OnValues() {
47  
48          Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
49  
50          final NumericalPropagatorBuilder propagatorBuilder =
51                          context.createBuilder(OrbitType.EQUINOCTIAL, PositionAngle.TRUE, false,
52                                                1.0e-6, 60.0, 0.001);
53  
54          // Create perfect right-ascension/declination measurements
55          final Propagator propagator = EstimationTestUtils.createPropagator(context.initialOrbit,
56                                                                             propagatorBuilder);
57          final List<ObservedMeasurement<?>> measurements =
58                          EstimationTestUtils.createMeasurements(propagator,
59                                                                 new AngularRaDecMeasurementCreator(context),
60                                                                 0.25, 3.0, 600.0);
61  
62          propagator.clearStepHandlers();
63  
64          // Prepare statistics for right-ascension/declination values difference
65          final StreamingStatistics raDiffStat  = new StreamingStatistics();
66          final StreamingStatistics decDiffStat = new StreamingStatistics();
67  
68          for (final ObservedMeasurement<?> measurement : measurements) {
69  
70              // Propagate to measurement date
71              final AbsoluteDate datemeas  = measurement.getDate();
72              SpacecraftState    state     = propagator.propagate(datemeas);
73              
74              // Estimate the RADEC value
75              final EstimatedMeasurement<?> estimated = measurement.estimate(0, 0, new SpacecraftState[] { state });
76              
77              // Store the difference between estimated and observed values in the stats
78              raDiffStat.addValue(FastMath.abs(estimated.getEstimatedValue()[0] - measurement.getObservedValue()[0]));
79              decDiffStat.addValue(FastMath.abs(estimated.getEstimatedValue()[1] - measurement.getObservedValue()[1]));
80          }
81  
82          // Mean and std errors check
83          Assert.assertEquals(0.0, raDiffStat.getMean(), 6.9e-11);
84          Assert.assertEquals(0.0, raDiffStat.getStandardDeviation(), 8.5e-11);
85          
86          Assert.assertEquals(0.0, decDiffStat.getMean(), 4.5e-11);
87          Assert.assertEquals(0.0, decDiffStat.getStandardDeviation(), 3e-11);
88      }
89      
90      /** Test the values of the state derivatives using a numerical.
91       * finite differences calculation as a reference
92       */
93      @Test
94      public void testStateDerivatives() {
95  
96          Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
97  
98          final NumericalPropagatorBuilder propagatorBuilder =
99                          context.createBuilder(OrbitType.EQUINOCTIAL, PositionAngle.TRUE, false,
100                                               1.0e-6, 60.0, 0.001);
101 
102         // create perfect right-ascension/declination measurements
103         final Propagator propagator = EstimationTestUtils.createPropagator(context.initialOrbit,
104                                                                            propagatorBuilder);
105         final List<ObservedMeasurement<?>> measurements =
106                         EstimationTestUtils.createMeasurements(propagator,
107                                                                new AngularRaDecMeasurementCreator(context),
108                                                                0.25, 3.0, 600.0);
109 
110         propagator.clearStepHandlers();
111 
112         // Compute measurements.
113         double[] RaerrorsP = new double[3 * measurements.size()];
114         double[] RaerrorsV = new double[3 * measurements.size()];
115         double[] DecerrorsP = new double[3 * measurements.size()];
116         double[] DecerrorsV = new double[3 * measurements.size()];
117         int RaindexP = 0;
118         int RaindexV = 0;
119         int DecindexP = 0;
120         int DecindexV = 0;
121 
122         for (final ObservedMeasurement<?> measurement : measurements) {
123 
124             // parameter corresponding to station position offset
125             final GroundStation stationParameter = ((AngularRaDec) measurement).getStation();
126 
127             // We intentionally propagate to a date which is close to the
128             // real spacecraft state but is *not* the accurate date, by
129             // compensating only part of the downlink delay. This is done
130             // in order to validate the partial derivatives with respect
131             // to velocity. If we had chosen the proper state date, the
132             // angular would have depended only on the current position but
133             // not on the current velocity.
134             final AbsoluteDate datemeas  = measurement.getDate();
135             SpacecraftState    state     = propagator.propagate(datemeas);
136             final Vector3D     stationP  = stationParameter.getOffsetToInertial(state.getFrame(), datemeas).transformPosition(Vector3D.ZERO);
137             final double       meanDelay = AbstractMeasurement.signalTimeOfFlight(state.getPVCoordinates(), stationP, datemeas);
138 
139             final AbsoluteDate date      = measurement.getDate().shiftedBy(-0.75 * meanDelay);
140                                state     = propagator.propagate(date);
141             final EstimatedMeasurement<?> estimated = measurement.estimate(0, 0, new SpacecraftState[] { state });
142             Assert.assertEquals(2, estimated.getParticipants().length);
143             final double[][]   jacobian  = estimated.getStateDerivatives(0);
144 
145             // compute a reference value using finite differences
146             final double[][] finiteDifferencesJacobian =
147                 Differentiation.differentiate(new StateFunction() {
148                     public double[] value(final SpacecraftState state) {
149                         return measurement.estimate(0, 0, new SpacecraftState[] { state }).getEstimatedValue();
150                     }
151                 }, measurement.getDimension(), propagator.getAttitudeProvider(), OrbitType.CARTESIAN,
152                    PositionAngle.TRUE, 250.0, 4).value(state);
153 
154             Assert.assertEquals(finiteDifferencesJacobian.length, jacobian.length);
155             Assert.assertEquals(finiteDifferencesJacobian[0].length, jacobian[0].length);
156 
157             final double smallest = FastMath.ulp((double) 1.0);
158 
159             for (int i = 0; i < jacobian.length; ++i) {
160                 for (int j = 0; j < jacobian[i].length; ++j) {
161                     double relativeError = FastMath.abs((finiteDifferencesJacobian[i][j] - jacobian[i][j]) /
162                                                               finiteDifferencesJacobian[i][j]);
163 
164                     if ((FastMath.sqrt(finiteDifferencesJacobian[i][j]) < smallest) && (FastMath.sqrt(jacobian[i][j]) < smallest) ){
165                         relativeError = 0.0;
166                     }
167 
168                     if (j < 3) {
169                         if (i == 0) {
170                             RaerrorsP[RaindexP++] = relativeError;
171                         } else {
172                             DecerrorsP[DecindexP++] = relativeError;
173                         }
174                     } else {
175                         if (i == 0) {
176                             RaerrorsV[RaindexV++] = relativeError;
177                         } else {
178                             DecerrorsV[DecindexV++] = relativeError;
179                         }
180                     }
181                 }
182             }
183         }
184 
185         // median errors on right-ascension
186         Assert.assertEquals(0.0, new Median().evaluate(RaerrorsP), 4.8e-11);
187         Assert.assertEquals(0.0, new Median().evaluate(RaerrorsV), 2.2e-5);
188 
189         // median errors on declination
190         Assert.assertEquals(0.0, new Median().evaluate(DecerrorsP), 1.5e-11);
191         Assert.assertEquals(0.0, new Median().evaluate(DecerrorsV), 5.4e-6);
192            }
193 
194     /** Test the values of the parameters' derivatives using a numerical
195      * finite differences calculation as a reference
196      */
197     @Test
198     public void testParameterDerivatives() {
199 
200         Context context = EstimationTestUtils.geoStationnaryContext("regular-data:potential:tides");
201 
202         final NumericalPropagatorBuilder propagatorBuilder =
203                         context.createBuilder(OrbitType.EQUINOCTIAL, PositionAngle.TRUE, false,
204                                               1.0e-6, 60.0, 0.001);
205 
206         // create perfect right-ascension/declination measurements
207         for (final GroundStation station : context.stations) {
208             station.getClockOffsetDriver().setSelected(true);
209             station.getEastOffsetDriver().setSelected(true);
210             station.getNorthOffsetDriver().setSelected(true);
211             station.getZenithOffsetDriver().setSelected(true);
212         }
213         final Propagator propagator = EstimationTestUtils.createPropagator(context.initialOrbit,
214                                                                            propagatorBuilder);
215         final List<ObservedMeasurement<?>> measurements =
216                         EstimationTestUtils.createMeasurements(propagator,
217                                                                new AngularRaDecMeasurementCreator(context),
218                                                                0.25, 3.0, 600.0);
219         propagator.clearStepHandlers();
220 
221         for (final ObservedMeasurement<?> measurement : measurements) {
222 
223             // parameter corresponding to station position offset
224             final GroundStation stationParameter = ((AngularRaDec) measurement).getStation();
225 
226             // We intentionally propagate to a date which is close to the
227             // real spacecraft state but is *not* the accurate date, by
228             // compensating only part of the downlink delay. This is done
229             // in order to validate the partial derivatives with respect
230             // to velocity. If we had chosen the proper state date, the
231             // angular would have depended only on the current position but
232             // not on the current velocity.
233             final AbsoluteDate    datemeas  = measurement.getDate();
234             final SpacecraftState stateini  = propagator.propagate(datemeas);
235             final Vector3D        stationP  = stationParameter.getOffsetToInertial(stateini.getFrame(), datemeas).transformPosition(Vector3D.ZERO);
236             final double          meanDelay = AbstractMeasurement.signalTimeOfFlight(stateini.getPVCoordinates(), stationP, datemeas);
237 
238             final AbsoluteDate    date      = measurement.getDate().shiftedBy(-0.75 * meanDelay);
239             final SpacecraftState state     = propagator.propagate(date);
240             final ParameterDriver[] drivers = new ParameterDriver[] {
241                 stationParameter.getEastOffsetDriver(),
242                 stationParameter.getNorthOffsetDriver(),
243                 stationParameter.getZenithOffsetDriver()
244             };
245             for (int i = 0; i < 3; ++i) {
246                 final double[] gradient  = measurement.estimate(0, 0, new SpacecraftState[] { state }).getParameterDerivatives(drivers[i]);
247                 Assert.assertEquals(2, measurement.getDimension());
248                 Assert.assertEquals(2, gradient.length);
249 
250                 for (final int k : new int[] {0, 1}) {
251                     final ParameterFunction dMkdP =
252                                     Differentiation.differentiate(new ParameterFunction() {
253                                         /** {@inheritDoc} */
254                                         @Override
255                                         public double value(final ParameterDriver parameterDriver) {
256                                             return measurement.estimate(0, 0, new SpacecraftState[] { state }).getEstimatedValue()[k];
257                                         }
258                                     }, 3, 50.0 * drivers[i].getScale());
259                     final double ref = dMkdP.value(drivers[i]);
260 
261                     if (ref > 1.e-12) {
262                         Assert.assertEquals(ref, gradient[k], 3e-9 * FastMath.abs(ref));
263                     }
264                 }
265             }
266         }
267     }
268 }
269