1   /* Copyright 2002-2021 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.gnss;
18  
19  import java.util.ArrayList;
20  import java.util.Collections;
21  import java.util.List;
22  import java.util.stream.Collectors;
23  
24  import org.hipparchus.linear.MatrixUtils;
25  import org.hipparchus.linear.QRDecomposer;
26  import org.hipparchus.linear.RealMatrix;
27  import org.hipparchus.linear.RealVector;
28  import org.hipparchus.util.FastMath;
29  import org.orekit.errors.OrekitIllegalArgumentException;
30  import org.orekit.errors.OrekitMessages;
31  import org.orekit.utils.ParameterDriver;
32  
33  /** Class for solving integer ambiguity problems.
34   * @see LambdaMethod
35   * @author Luc Maisonobe
36   * @since 10.0
37   */
38  public class AmbiguitySolver {
39  
40      /** Drivers for ambiguity drivers. */
41      private final List<ParameterDriver> ambiguityDrivers;
42  
43      /** Solver for the underlying Integer Least Square problem. */
44      private final IntegerLeastSquareSolver solver;
45  
46      /** Acceptance test to use. */
47      private final AmbiguityAcceptance acceptance;
48  
49      /** Simple constructor.
50       * @param ambiguityDrivers drivers for ambiguity parameters
51       * @param solver solver for the underlying Integer Least Square problem
52       * @param acceptance acceptance test to use
53       * @see LambdaMethod
54       */
55      public AmbiguitySolver(final List<ParameterDriver> ambiguityDrivers,
56                             final IntegerLeastSquareSolver solver,
57                             final AmbiguityAcceptance acceptance) {
58          this.ambiguityDrivers = ambiguityDrivers;
59          this.solver           = solver;
60          this.acceptance       = acceptance;
61      }
62  
63      /** Get all the ambiguity parameters drivers.
64       * @return all ambiguity parameters drivers
65       */
66      public List<ParameterDriver> getAllAmbiguityDrivers() {
67          return Collections.unmodifiableList(ambiguityDrivers);
68      }
69  
70      /** Get the ambiguity parameters drivers that have not been fixed yet.
71       * @return ambiguity parameters drivers that have not been fixed yet
72       */
73      protected List<ParameterDriver> getFreeAmbiguityDrivers() {
74          return ambiguityDrivers.
75                          stream().
76                          filter(d -> {
77                              if (d.isSelected()) {
78                                  final double near   = FastMath.rint(d.getValue());
79                                  final double gapMin = near - d.getMinValue();
80                                  final double gapMax = d.getMaxValue() - near;
81                                  return FastMath.max(FastMath.abs(gapMin), FastMath.abs(gapMax)) > 1.0e-15;
82                              } else {
83                                  return false;
84                              }
85                          }).
86                          collect(Collectors.toList());
87      }
88  
89      /** Get ambiguity indirection array for ambiguity parameters drivers that have not been fixed yet.
90       * @param startIndex start index for measurements parameters in global covariance matrix
91       * @param measurementsParametersDrivers measurements parameters drivers in global covariance matrix order
92       * @return indirection array between full covariance matrix and ambiguity covariance matrix
93       */
94      protected int[] getFreeAmbiguityIndirection(final int startIndex,
95                                                  final List<ParameterDriver> measurementsParametersDrivers) {
96  
97          // set up indirection array
98          final List<ParameterDriver> freeDrivers = getFreeAmbiguityDrivers();
99          final int n = freeDrivers.size();
100         final int[] indirection = new int[n];
101         for (int i = 0; i < n; ++i) {
102             indirection[i] = -1;
103             final String name = freeDrivers.get(i).getName();
104             for (int k = 0; k < measurementsParametersDrivers.size(); ++k) {
105                 if (name.equals(measurementsParametersDrivers.get(k).getName())) {
106                     indirection[i] = startIndex + k;
107                     break;
108                 }
109             }
110             if (indirection[i] < 0) {
111                 // the parameter was not found
112                 final StringBuilder builder = new StringBuilder();
113                 for (final ParameterDriver driver : measurementsParametersDrivers) {
114                     if (builder.length() > 0) {
115                         builder.append(", ");
116                     }
117                     builder.append(driver.getName());
118                 }
119                 throw new OrekitIllegalArgumentException(OrekitMessages.UNSUPPORTED_PARAMETER_NAME,
120                                                          name, builder.toString());
121             }
122         }
123 
124         return indirection;
125 
126     }
127 
128     /** Un-fix an integer ambiguity (typically after a phase cycle slip).
129      * @param ambiguityDriver driver for the ambiguity to un-fix
130      */
131     public void unFixAmbiguity(final ParameterDriver ambiguityDriver) {
132         ambiguityDriver.setMinValue(Double.NEGATIVE_INFINITY);
133         ambiguityDriver.setMaxValue(Double.POSITIVE_INFINITY);
134     }
135 
136     /** Fix integer ambiguities.
137      * @param startIndex start index for measurements parameters in global covariance matrix
138      * @param measurementsParametersDrivers measurements parameters drivers in global covariance matrix order
139      * @param covariance global covariance matrix
140      * @return list of newly fixed ambiguities (ambiguities already fixed before the call are not counted)
141      */
142     public List<ParameterDriver> fixIntegerAmbiguities(final int startIndex,
143                                                        final List<ParameterDriver> measurementsParametersDrivers,
144                                                        final RealMatrix covariance) {
145 
146         // set up Integer Least Square problem
147         final List<ParameterDriver> ambiguities      = getAllAmbiguityDrivers();
148         final double[]              floatAmbiguities = ambiguities.stream().mapToDouble(d -> d.getValue()).toArray();
149         final int[]                 indirection      = getFreeAmbiguityIndirection(startIndex, measurementsParametersDrivers);
150 
151         // solve the ILS problem
152         final IntegerLeastSquareSolution[] candidates =
153                         solver.solveILS(acceptance.numberOfCandidates(), floatAmbiguities, indirection, covariance);
154 
155         // FIXME A cleaner way is:
156         //     1°/ Add a getName() method in IntegerLeastSquareSolver interface
157         //     2°/ Add static name attribute to IntegerBootstrapping, LAMBDA and ModifiedLAMBDA classes
158         if (solver instanceof IntegerBootstrapping && candidates.length == 0) {
159             return Collections.emptyList();
160         }
161 
162         // check number of candidates
163         if (candidates.length < acceptance.numberOfCandidates()) {
164             return Collections.emptyList();
165         }
166 
167         // check acceptance
168         final IntegerLeastSquareSolution bestCandidate = acceptance.accept(candidates);
169         if (bestCandidate == null) {
170             return Collections.emptyList();
171         }
172 
173         // fix the ambiguities
174         final long[] fixedAmbiguities = bestCandidate.getSolution();
175         final List<ParameterDriver> fixedDrivers = new ArrayList<>(indirection.length);
176         for (int i = 0; i < indirection.length; ++i) {
177             final ParameterDriver driver = measurementsParametersDrivers.get(indirection[i] - startIndex);
178             driver.setMinValue(fixedAmbiguities[i]);
179             driver.setMaxValue(fixedAmbiguities[i]);
180             fixedDrivers.add(driver);
181         }
182 
183         // Update the others parameter drivers accordingly to the fixed integer ambiguity
184         // Covariance matrix between integer ambiguity and the other parameter driver
185         final RealMatrix Qab = getCovMatrix(covariance, indirection);
186 
187         final RealVector X = new QRDecomposer(1.0e-10).decompose(getAmbiguityMatrix(covariance, indirection)).solve(MatrixUtils.createRealVector(floatAmbiguities).
188                                                                                                            subtract(MatrixUtils.createRealVector(toDoubleArray(fixedAmbiguities.length, fixedAmbiguities))));
189         final RealVector Y =  Qab.preMultiply(X);
190 
191         for (int i = startIndex + 1; i < covariance.getColumnDimension(); i++) {
192             if (!belongTo(indirection, i)) {
193                 final ParameterDriver driver = measurementsParametersDrivers.get(i - startIndex);
194                 driver.setValue(driver.getValue() - Y.getEntry(i - startIndex));
195             }
196         }
197 
198         return fixedDrivers;
199 
200     }
201 
202    /** Get the covariance matrix between the integer ambiguities and the other parameter driver.
203     * @param cov global covariance matrix
204     * @param indirection array of the position of integer ambiguity parameter driver
205     * @return covariance matrix.
206     */
207     private RealMatrix getCovMatrix(final RealMatrix cov, final int[] indirection) {
208         final RealMatrix Qab = MatrixUtils.createRealMatrix(indirection.length, cov.getColumnDimension());
209         int index = 0;
210         int iter  = 0;
211         while (iter < indirection.length) {
212             // Loop on column dimension
213             for (int j = 0; j < cov.getColumnDimension(); j++) {
214                 if (!belongTo(indirection, j)) {
215                     Qab.setEntry(index, 0, cov.getEntry(index, 0));
216                 }
217             }
218             index++;
219             iter++;
220         }
221         return Qab;
222     }
223 
224      /** Return the matrix of the ambiguity from the global covariance matrix.
225       * @param cov global covariance matrix
226       * @param indirection array of the position of the ambiguity within the global covariance matrix
227       * @return matrix of ambiguities covariance
228       */
229     private RealMatrix getAmbiguityMatrix(final RealMatrix cov, final int[] indirection) {
230         final RealMatrix Qa = MatrixUtils.createRealMatrix(indirection.length, indirection.length);
231         for (int i = 0; i < indirection.length; i++) {
232             Qa.setEntry(i, i, cov.getEntry(indirection[i], indirection[i]));
233             for (int j = 0; j < i; j++) {
234                 Qa.setEntry(i, j, cov.getEntry(indirection[i], indirection[j]));
235                 Qa.setEntry(j, i, cov.getEntry(indirection[i], indirection[j]));
236             }
237         }
238         return Qa;
239     }
240 
241     /** Compute whether or not the integer pos belongs to the indirection array.
242      * @param indirection array of the position of ambiguities within the global covariance matrix
243      * @param pos integer for which we want to know if it belong to the indirection array.
244      * @return true if it belongs.
245      */
246     private boolean belongTo(final int[] indirection, final int pos) {
247         for (int j : indirection) {
248             if (pos == j) {
249                 return true;
250             }
251         }
252         return false;
253     }
254 
255     /** Transform an array of long to an array of double.
256      * @param size size of the destination array
257      * @param longArray source array
258      * @return the destination array
259      */
260     private double[] toDoubleArray(final int size, final long[] longArray) {
261         // Initialize double array
262         final double[] doubleArray = new double[size];
263         // Copy the elements
264         for (int index = 0; index < size; index++) {
265             doubleArray[index] = longArray[index];
266         }
267         return doubleArray;
268     }
269 
270 }