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  
18  package org.orekit.rugged.adjustment;
19  
20  import java.util.ArrayList;
21  import java.util.HashMap;
22  import java.util.HashSet;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Set;
26  
27  import org.hipparchus.Field;
28  import org.hipparchus.analysis.differentiation.Gradient;
29  import org.hipparchus.analysis.differentiation.GradientField;
30  import org.hipparchus.optim.ConvergenceChecker;
31  import org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresProblem;
32  import org.hipparchus.optim.nonlinear.vector.leastsquares.MultivariateJacobianFunction;
33  import org.hipparchus.optim.nonlinear.vector.leastsquares.ParameterValidator;
34  import org.orekit.rugged.adjustment.measurements.Observables;
35  import org.orekit.rugged.errors.RuggedException;
36  import org.orekit.rugged.errors.RuggedMessages;
37  import org.orekit.rugged.linesensor.LineSensor;
38  import org.orekit.rugged.utils.DerivativeGenerator;
39  import org.orekit.utils.ParameterDriver;
40  
41  /**
42   * Builder for optimization problem.
43   * <p>
44   * Constructs the optimization problem defined by a set of measurement and sensors.
45   * </p>
46   * @author Jonathan Guinet
47   * @author Guylaine Prat
48   * @since 2.0
49   */
50  abstract class OptimizationProblemBuilder {
51  
52      /** Margin used in parameters estimation for the inverse location lines range. */
53      protected static final int ESTIMATION_LINE_RANGE_MARGIN = 100;
54  
55      /** Gradient generator.*/
56      private final DerivativeGenerator<Gradient> generator;
57  
58      /** Parameter drivers list. */
59      private final List<ParameterDriver> drivers;
60  
61      /** Number of parameters to refine. */
62      private final int nbParams;
63  
64      /** Measurements. */
65      private Observables measurements;
66  
67      /** Sensors list. */
68      private final List<LineSensor> sensors;
69  
70      /** Constructor.
71       * @param sensors list of sensors to refine
72       * @param measurements set of observables
73       */
74      OptimizationProblemBuilder(final List<LineSensor> sensors, final Observables measurements) {
75  
76          this.generator = this.createGenerator(sensors);
77          this.drivers = this.generator.getSelected();
78          this.nbParams = this.drivers.size();
79          if (this.nbParams == 0) {
80              throw new RuggedException(RuggedMessages.NO_PARAMETERS_SELECTED);
81          }
82          this.measurements = measurements;
83          this.sensors = sensors;
84      }
85  
86      /** Least squares problem builder.
87       * @param maxEvaluations maximum number of evaluations
88       * @param convergenceThreshold convergence threshold
89       * @return the least squares problem
90       */
91  
92      public abstract LeastSquaresProblem build(int maxEvaluations, double convergenceThreshold);
93  
94      /** Create the convergence check.
95       * <p>
96       * check LInf distance of parameters variation between previous and current iteration
97       * </p>
98       * @param parametersConvergenceThreshold convergence threshold
99       * @return the checker
100      */
101     final ConvergenceChecker<LeastSquaresProblem.Evaluation>
102                             createChecker(final double parametersConvergenceThreshold) {
103 
104         final ConvergenceChecker<LeastSquaresProblem.Evaluation> checker = (iteration, previous, current)
105             -> current.getPoint().getLInfDistance(previous.getPoint()) <= parametersConvergenceThreshold;
106 
107         return checker;
108     }
109 
110     /** Create start points for optimization algorithm.
111      * @return start parameters values (normalized)
112      */
113     final double[] createStartTab() {
114 
115         // Get start points (as a normalized value)
116         final double[] start = new double[this.nbParams];
117         int iStart = 0;
118         for (final ParameterDriver driver : this.drivers) {
119             start[iStart++] = driver.getNormalizedValue();
120         }
121         return start;
122     }
123 
124     /** Create targets and weights of optimization problem. */
125     protected abstract void createTargetAndWeight();
126 
127     /** Create the model function value and its Jacobian.
128      * @return the model function value and its Jacobian
129      */
130     protected abstract MultivariateJacobianFunction createFunction();
131 
132     /** Parse the observables to select mapping .*/
133     protected abstract void initMapping();
134 
135     /** Create parameter validator.
136      * @return parameter validator
137      */
138     final ParameterValidator createParameterValidator() {
139 
140         // Prevent parameters to exceed their prescribed bounds
141         final ParameterValidator validator = params -> {
142             int i = 0;
143             for (final ParameterDriver driver : this.drivers) {
144 
145                 // let the parameter handle min/max clipping
146                 driver.setNormalizedValue(params.getEntry(i));
147                 params.setEntry(i++, driver.getNormalizedValue());
148             }
149             return params;
150         };
151 
152         return validator;
153     }
154 
155     /** Create the generator for {@link Gradient} instances.
156      * @param selectedSensors list of sensors referencing the parameters drivers
157      * @return a new generator
158      */
159     private DerivativeGenerator<Gradient> createGenerator(final List<LineSensor> selectedSensors) {
160 
161         // Initialize set of drivers name
162         final Set<String> names = new HashSet<>();
163 
164         // Get the drivers name
165         for (final LineSensor sensor : selectedSensors) {
166 
167             // Get the drivers name for the sensor
168             sensor.getParametersDrivers().forEach(driver -> {
169 
170                 // Add the name of the driver to the set of drivers name
171                 if (names.contains(driver.getName()) == false) {
172                     names.add(driver.getName());
173                 }
174             });
175         }
176 
177         // Set up generator list and map
178         final List<ParameterDriver> selected = new ArrayList<>();
179         final Map<String, Integer> map = new HashMap<>();
180 
181         // Get the list of selected drivers
182         for (final LineSensor sensor : selectedSensors) {
183 
184             sensor.getParametersDrivers().filter(driver -> driver.isSelected()).forEach(driver -> {
185                 if (map.get(driver.getName()) == null) {
186                     map.put(driver.getName(), map.size());
187                     selected.add(driver);
188                 }
189             });
190         }
191 
192         // gradient Generator
193         final GradientField field = GradientField.getField(map.size());
194         return new DerivativeGenerator<Gradient>() {
195 
196             /** {@inheritDoc} */
197             @Override
198             public List<ParameterDriver> getSelected() {
199                 return selected;
200             }
201 
202             /** {@inheritDoc} */
203             @Override
204             public Gradient constant(final double value) {
205                 return Gradient.constant(map.size(), value);
206             }
207 
208             /** {@inheritDoc} */
209             @Override
210             public Gradient variable(final ParameterDriver driver) {
211                 final Integer index = map.get(driver.getName());
212                 if (index == null) {
213                     return constant(driver.getValue());
214                 } else {
215                     return Gradient.variable(map.size(), index.intValue(), driver.getValue());
216                 }
217             }
218 
219             /** {@inheritDoc} */
220             @Override
221             public Field<Gradient> getField() {
222                 return field;
223             }
224 
225         };
226     }
227 
228     /** Get the sensors list.
229      * @return the sensors list
230      */
231     protected List<LineSensor> getSensors() {
232         return sensors;
233     }
234 
235     /** Get the number of parameters to refine.
236      * @return the number of parameters to refine
237      */
238     protected final int getNbParams() {
239         return this.nbParams;
240     }
241 
242     /**
243      * Get the parameters drivers list.
244      * @return the selected list of parameters driver
245      */
246     protected final List<ParameterDriver> getDrivers() {
247         return this.drivers;
248     }
249 
250     /**
251      * Get the derivative structure generator.
252      * @return the derivative structure generator.
253      */
254     protected final DerivativeGenerator<Gradient> getGenerator() {
255         return this.generator;
256     }
257 
258     /** Get the measurements.
259      * @return the measurements
260      */
261     protected Observables getMeasurements() {
262         return measurements;
263     }
264 }