1   /* Copyright 2013-2025 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.rugged.adjustment;
18  
19  import java.lang.reflect.Field;
20  import java.util.ArrayList;
21  import java.util.Collection;
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Map.Entry;
26  import java.util.Set;
27  
28  import org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresOptimizer.Optimum;
29  import org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresProblem;
30  import org.junit.jupiter.api.AfterEach;
31  import org.junit.jupiter.api.Assertions;
32  import org.junit.jupiter.api.BeforeEach;
33  import org.junit.jupiter.api.Test;
34  import org.orekit.rugged.TestUtils;
35  import org.orekit.rugged.adjustment.measurements.Observables;
36  import org.orekit.rugged.adjustment.measurements.SensorToSensorMapping;
37  import org.orekit.rugged.adjustment.util.InitInterRefiningTest;
38  import org.orekit.rugged.api.Rugged;
39  import org.orekit.rugged.errors.RuggedException;
40  import org.orekit.rugged.errors.RuggedMessages;
41  import org.orekit.rugged.linesensor.LineSensor;
42  import org.orekit.rugged.linesensor.SensorPixel;
43  
44  public class InterSensorOptimizationProblemBuilderTest {
45  
46      @BeforeEach
47      public void setUp() {
48  
49          try {
50              refiningTest = new InitInterRefiningTest();
51              refiningTest.initRefiningTest();
52  
53              ruggedList = refiningTest.getRuggedList();
54  
55              earthConstraintWeight = 0.1;
56  
57              measurements = refiningTest.generateNoisyPoints(lineSampling, pixelSampling, earthConstraintWeight, false);
58              numberOfParameters = refiningTest.getParameterToAdjust();
59  
60          }  catch (RuggedException re) {
61              Assertions.fail(re.getLocalizedMessage());
62          }
63      }
64  
65      @Test
66      public void testEstimateFreeParameters() throws SecurityException, IllegalArgumentException {
67  
68          AdjustmentContext adjustmentContext = new AdjustmentContext(ruggedList, measurements);
69  
70          List<String> ruggedNameList = new ArrayList<>();
71          for(Rugged rugged : ruggedList) {
72              ruggedNameList.add(rugged.getName());
73          }
74          final int maxIterations = 100;
75          final double convergenceThreshold = 1.e-7;
76  
77          Optimum optimum = adjustmentContext.estimateFreeParameters(ruggedNameList, maxIterations, convergenceThreshold);
78  
79          Assertions.assertTrue(optimum.getIterations() < maxIterations);
80  
81          // The default optimizer is a Gauss Newton.
82          // For Gauss Newton, the number of evaluations is equal to the number of iterations
83          Assertions.assertTrue(optimum.getEvaluations() == optimum.getIterations());
84  
85          final double expectedMaxValue = 1.924769e-03;
86          Assertions.assertEquals(expectedMaxValue, optimum.getResiduals().getMaxValue(), 1.0e-6);
87  
88          final double expectedRMS = 0.069302;
89          Assertions.assertEquals(expectedRMS, optimum.getRMS(), 1.0e-6);
90  
91          final double expectedCost = 3.596994;
92          Assertions.assertEquals(expectedCost, optimum.getCost(), 2.5e-6);
93  
94          Assertions.assertTrue(numberOfParameters == optimum.getPoint().getDimension());
95          
96          final int sensorToSensorMappingSize = 1347;
97          Collection<SensorToSensorMapping> ssm = measurements.getInterMappings();
98          Iterator<SensorToSensorMapping> it = ssm.iterator();
99          while (it.hasNext()) {
100             SensorToSensorMapping ssmit = it.next();
101             Assertions.assertTrue(sensorToSensorMappingSize == ssmit.getMapping().size());
102         }        
103         Assertions.assertTrue(sensorToSensorMappingSize*2 == optimum.getResiduals().getDimension());
104 
105     }
106     
107     @Test
108     public void testEarthConstraintPostponed() {
109 
110         // Get the measurements as computed in other tests
111         Collection<SensorToSensorMapping> sensorToSensorMapping = measurements.getInterMappings();
112         int nbModels = measurements.getNbModels();
113 
114         // Recompute the measurements in another way ... but that must be the same after all
115         Observables measurementsPostponed = refiningTest.generateNoisyPoints(lineSampling, pixelSampling, earthConstraintWeight, true);
116         Collection<SensorToSensorMapping> sensorToSensorMappingPostponed = measurementsPostponed.getInterMappings();
117         int nbModelsPostponed = measurementsPostponed.getNbModels();
118 
119         // Compare the two collections of measurements
120         Assertions.assertEquals(nbModels, nbModelsPostponed);
121 
122         Assertions.assertEquals(sensorToSensorMapping.size(), sensorToSensorMappingPostponed.size());
123 
124         // There is only one item
125         SensorToSensorMapping arraySensorToSensorMapping          = (SensorToSensorMapping) sensorToSensorMapping.toArray()[0];
126         SensorToSensorMapping arraySensorToSensorMappingPostponed = (SensorToSensorMapping) sensorToSensorMappingPostponed.toArray()[0];
127 
128         List<Double> listBody = arraySensorToSensorMapping.getBodyDistances();
129 
130         // Convert List<Double> to double[]
131         double[] arrayBody = listBody.stream().mapToDouble(Double::doubleValue).toArray(); //via method reference
132         // Explanations:
133         //      get the Stream<Double> from the list
134         //      map each double instance to its primitive value, resulting in a DoubleStream
135         //      call toArray() to get the array.
136         // Other method:       double[] arrayBody = listBody.stream().mapToDouble(d -> d).toArray(); //identity function, Java unboxes automatically to get the double value
137 
138         List<Double> listBodyPostponed = arraySensorToSensorMappingPostponed.getBodyDistances();
139         double[] arrayBodyPostponed  = listBodyPostponed.stream().mapToDouble(Double::doubleValue).toArray();
140 
141         Assertions.assertEquals(listBody.size(), listBodyPostponed.size());
142         Assertions.assertArrayEquals(arrayBody, arrayBodyPostponed, 3.e-3);
143 
144         List<Double> listLos = arraySensorToSensorMapping.getLosDistances();
145         double[] arrayLos = listLos.stream().mapToDouble(Double::doubleValue).toArray();
146         List<Double> listLosPostponed = arraySensorToSensorMappingPostponed.getLosDistances();
147         double[] arrayLosPostponed = listLosPostponed.stream().mapToDouble(Double::doubleValue).toArray();
148 
149         Assertions.assertEquals(listLos.size(), listLosPostponed.size());
150         Assertions.assertArrayEquals(arrayLos, arrayLosPostponed, 1.e-6);
151 
152         // Check if the two set are the same
153         Set<Entry<SensorPixel, SensorPixel>> mapping          = arraySensorToSensorMapping.getMapping();
154         Set<Entry<SensorPixel, SensorPixel>> mappingPostponed = arraySensorToSensorMappingPostponed.getMapping();
155 
156         Iterator<Entry<SensorPixel, SensorPixel>> itMapping = mapping.iterator();
157         while(itMapping.hasNext()) {
158             Entry<SensorPixel, SensorPixel> current = itMapping.next();
159             SensorPixel key = current.getKey();
160             SensorPixel value = current.getValue();
161 
162             // Will search in mappingPostponed if we can find the (key,value) found in mapping 
163             Boolean found = false;
164             Iterator<Entry<SensorPixel, SensorPixel>> itMappingPost = mappingPostponed.iterator();
165             while(itMappingPost.hasNext()) {
166                 Entry<SensorPixel, SensorPixel> currentPost = itMappingPost.next();
167                 SensorPixel keyPost = currentPost.getKey();
168                 SensorPixel valuePost = currentPost.getValue();
169 
170                 // Comparison of each SensorPixel (for the key part and the value part)
171                 if (TestUtils.sameSensorPixels(key, keyPost, 3.e-3) &&
172                     TestUtils.sameSensorPixels(value, valuePost, 3.e-3)) {
173                     // we found a match ...
174                     found = true;
175                 }
176             } // end iteration on mappingPostponed
177 
178             if (!found) { // the current (key,value) of the mapping was not found in the mappingPostponed
179                 Assertions.assertTrue(found);
180             }
181         } // end on iteration on mapping
182 
183         Assertions.assertEquals(arraySensorToSensorMapping.getRuggedNameA(),arraySensorToSensorMappingPostponed.getRuggedNameA());
184         Assertions.assertEquals(arraySensorToSensorMapping.getRuggedNameB(),arraySensorToSensorMappingPostponed.getRuggedNameB());
185         Assertions.assertEquals(arraySensorToSensorMapping.getSensorNameA(),arraySensorToSensorMappingPostponed.getSensorNameA());
186         Assertions.assertEquals(arraySensorToSensorMapping.getSensorNameB(),arraySensorToSensorMappingPostponed.getSensorNameB());
187     } 
188     
189     @Test
190     public void testDefaultRuggedNames() {
191         
192         // In that case there are no body distance set at construction
193 
194         // Generate intermapping with simple constructor of SensorToSensorMapping with Earth Constraint Weight given at construction
195         Observables measurementsWithWeight = refiningTest.generateSimpleInterMapping(lineSampling, pixelSampling, earthConstraintWeight, false);
196         Collection<SensorToSensorMapping> sensorToSensorMappingWithWeight = measurementsWithWeight.getInterMappings();
197         int nbModelsWithWeight = measurementsWithWeight.getNbModels();
198 
199         // Generate intermapping with simple constructor of SensorToSensorMapping with Earth Constraint Weight given after construction
200         Observables measurementsWithoutWeight = refiningTest.generateSimpleInterMapping(lineSampling, pixelSampling, earthConstraintWeight, true);        
201         Collection<SensorToSensorMapping> sensorToSensorMappingPostponed = measurementsWithoutWeight.getInterMappings();
202         int nbModelsPostponed = measurementsWithoutWeight.getNbModels();
203 
204         // Compare the two collections of measurements
205         Assertions.assertEquals(nbModelsWithWeight, nbModelsPostponed);
206 
207         Assertions.assertEquals(sensorToSensorMappingWithWeight.size(), sensorToSensorMappingPostponed.size());
208 
209         // There is only one item
210         SensorToSensorMapping arraySensorToSensorMappingWithWeight          = (SensorToSensorMapping) sensorToSensorMappingWithWeight.toArray()[0];
211         SensorToSensorMapping arraySensorToSensorMappingPostponed = (SensorToSensorMapping) sensorToSensorMappingPostponed.toArray()[0];
212 
213 
214         List<Double> listLosWithWeight = arraySensorToSensorMappingWithWeight.getLosDistances();
215         double[] arrayLosWithWeight = listLosWithWeight.stream().mapToDouble(Double::doubleValue).toArray();
216         List<Double> listLosPostponed = arraySensorToSensorMappingPostponed.getLosDistances();
217         double[] arrayLosPostponed = listLosPostponed.stream().mapToDouble(Double::doubleValue).toArray();
218 
219         Assertions.assertEquals(listLosWithWeight.size(), listLosPostponed.size());
220         Assertions.assertArrayEquals(arrayLosWithWeight, arrayLosPostponed, 1.e-6);
221 
222         // Check if the two set are the same
223         Set<Entry<SensorPixel, SensorPixel>> mappingWithWeight = arraySensorToSensorMappingWithWeight.getMapping();
224         Set<Entry<SensorPixel, SensorPixel>> mappingPostponed  = arraySensorToSensorMappingPostponed.getMapping();
225 
226         Iterator<Entry<SensorPixel, SensorPixel>> itMapping = mappingWithWeight.iterator();
227         while(itMapping.hasNext()) {
228             Entry<SensorPixel, SensorPixel> current = itMapping.next();
229             SensorPixel key = current.getKey();
230             SensorPixel value = current.getValue();
231 
232             // Will search in mappingPostponed if we can find the (key,value) found in mapping 
233             boolean found = false;
234             Iterator<Entry<SensorPixel, SensorPixel>> itMappingPost = mappingPostponed.iterator();
235             while(itMappingPost.hasNext()) {
236                 Entry<SensorPixel, SensorPixel> currentPost = itMappingPost.next();
237                 SensorPixel keyPost = currentPost.getKey();
238                 SensorPixel valuePost = currentPost.getValue();
239 
240                 // Comparison of each SensorPixel (for the key part and the value part)
241                 if (TestUtils.sameSensorPixels(key, keyPost, 1.e-3) &&
242                     TestUtils.sameSensorPixels(value, valuePost, 1.e-3)) {
243                     // we found a match ...
244                     found = true;
245                 }
246             } // end iteration on mappingPostponed
247 
248             if (!found) { // the current (key,value) of the mapping was not found in the mappingPostponed
249                 Assertions.assertTrue(found);
250             }
251         } // end on iteration on mapping
252 
253         Assertions.assertEquals(arraySensorToSensorMappingWithWeight.getRuggedNameA(),arraySensorToSensorMappingPostponed.getRuggedNameA());
254         Assertions.assertEquals(arraySensorToSensorMappingWithWeight.getRuggedNameB(),arraySensorToSensorMappingPostponed.getRuggedNameB());
255         Assertions.assertEquals(arraySensorToSensorMappingWithWeight.getSensorNameA(),arraySensorToSensorMappingPostponed.getSensorNameA());
256         Assertions.assertEquals(arraySensorToSensorMappingWithWeight.getSensorNameB(),arraySensorToSensorMappingPostponed.getSensorNameB());
257     }
258     
259     @Test
260     public void testNoReferenceMapping() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
261 
262         try {
263             final int maxIterations = 120;
264             final double convergenceThreshold = 1.e-7;
265 
266             final List<LineSensor> selectedSensors = new ArrayList<>();
267             for (Rugged rugged : ruggedList) {
268                 selectedSensors.addAll(rugged.getLineSensors());
269             }
270             final InterSensorsOptimizationProblemBuilder interSensorsOptimizationProblem = 
271                     new InterSensorsOptimizationProblemBuilder(selectedSensors, measurements, ruggedList);
272 
273             Field sensorToSensorMapping = interSensorsOptimizationProblem.getClass().getDeclaredField("sensorToSensorMappings");
274             sensorToSensorMapping.setAccessible(true);
275             sensorToSensorMapping.set(interSensorsOptimizationProblem,new ArrayList<SensorToSensorMapping>());
276 
277             interSensorsOptimizationProblem.build(maxIterations, convergenceThreshold);
278             Assertions.fail("An exception should have been thrown");
279 
280         } catch  (RuggedException re) {
281             Assertions.assertEquals(RuggedMessages.NO_REFERENCE_MAPPINGS,re.getSpecifier());
282         }
283     }
284 
285     @Test
286     public void testInvalidRuggedNames() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
287 
288             final int maxIterations = 120;
289             final double convergenceThreshold = 1.e-7;
290 
291             final List<LineSensor> selectedSensors = new ArrayList<>();
292             for (Rugged rugged : ruggedList) {
293                 selectedSensors.addAll(rugged.getLineSensors());
294             }
295             final InterSensorsOptimizationProblemBuilder interSensorsOptimizationProblem = 
296                     new InterSensorsOptimizationProblemBuilder(selectedSensors, measurements, ruggedList);
297 
298             Field ruggedMapField = interSensorsOptimizationProblem.getClass().getDeclaredField("ruggedMap");
299             ruggedMapField.setAccessible(true);
300             @SuppressWarnings("unchecked")
301             Map<String,Rugged> ruggedMap = (Map<String,Rugged>) ruggedMapField.get(interSensorsOptimizationProblem);
302             
303             // Set first RuggedB to null to get the right exception ...
304             try {
305                 ruggedMap.put("RuggedB",null);
306 
307                 ruggedMapField.set(interSensorsOptimizationProblem,ruggedMap);
308 
309                 final LeastSquareAdjuster adjuster = new LeastSquareAdjuster(OptimizerId.GAUSS_NEWTON_QR);
310                 LeastSquaresProblem theProblem = interSensorsOptimizationProblem.build(maxIterations, convergenceThreshold);
311                 adjuster.optimize(theProblem);
312                 Assertions.fail("An exception should have been thrown");
313 
314             } catch  (RuggedException re) {
315                 Assertions.assertEquals(RuggedMessages.INVALID_RUGGED_NAME,re.getSpecifier());
316             }
317             
318             // Then set RuggedA to null to get the right exception ...
319             try {
320                 ruggedMap.put("RuggedA",null);
321 
322                 ruggedMapField.set(interSensorsOptimizationProblem,ruggedMap);
323 
324                 final LeastSquareAdjuster adjuster = new LeastSquareAdjuster(OptimizerId.GAUSS_NEWTON_QR);
325                 LeastSquaresProblem theProblem = interSensorsOptimizationProblem.build(maxIterations, convergenceThreshold);
326                 adjuster.optimize(theProblem);
327                 Assertions.fail("An exception should have been thrown");
328 
329             } catch  (RuggedException re) {
330                 Assertions.assertEquals(RuggedMessages.INVALID_RUGGED_NAME,re.getSpecifier());
331             }
332     }
333 
334     
335     @AfterEach
336     public void tearDown() {
337         measurements = null;
338         ruggedList = null;
339     }
340 
341     private InitInterRefiningTest refiningTest;
342     private Observables measurements;
343     private List<Rugged> ruggedList;
344     private int numberOfParameters;
345     private double earthConstraintWeight; 
346     
347     private  final int lineSampling = 1000;
348     private final int pixelSampling = 1000;
349 }