AmbiguitySolver.java

  1. /* Copyright 2002-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.estimation.measurements.gnss;

  18. import java.util.ArrayList;
  19. import java.util.Collections;
  20. import java.util.List;
  21. import java.util.stream.Collectors;

  22. import org.hipparchus.linear.MatrixUtils;
  23. import org.hipparchus.linear.QRDecomposer;
  24. import org.hipparchus.linear.RealMatrix;
  25. import org.hipparchus.linear.RealVector;
  26. import org.hipparchus.util.FastMath;
  27. import org.orekit.errors.OrekitIllegalArgumentException;
  28. import org.orekit.errors.OrekitMessages;
  29. import org.orekit.utils.ParameterDriver;
  30. import org.orekit.utils.TimeSpanMap.Span;

  31. /** Class for solving integer ambiguity problems.
  32.  * @see LambdaMethod
  33.  * @author Luc Maisonobe
  34.  * @since 10.0
  35.  */
  36. public class AmbiguitySolver {

  37.     /** Drivers for ambiguity drivers. */
  38.     private final List<ParameterDriver> ambiguityDrivers;

  39.     /** Solver for the underlying Integer Least Square problem. */
  40.     private final IntegerLeastSquareSolver solver;

  41.     /** Acceptance test to use. */
  42.     private final AmbiguityAcceptance acceptance;

  43.     /** Simple constructor.
  44.      * @param ambiguityDrivers drivers for ambiguity parameters
  45.      * @param solver solver for the underlying Integer Least Square problem
  46.      * @param acceptance acceptance test to use
  47.      * @see LambdaMethod
  48.      */
  49.     public AmbiguitySolver(final List<ParameterDriver> ambiguityDrivers,
  50.                            final IntegerLeastSquareSolver solver,
  51.                            final AmbiguityAcceptance acceptance) {
  52.         this.ambiguityDrivers = ambiguityDrivers;
  53.         this.solver           = solver;
  54.         this.acceptance       = acceptance;
  55.     }

  56.     /** Get all the ambiguity parameters drivers.
  57.      * @return all ambiguity parameters drivers
  58.      */
  59.     public List<ParameterDriver> getAllAmbiguityDrivers() {
  60.         return Collections.unmodifiableList(ambiguityDrivers);
  61.     }

  62.     /** Get the ambiguity parameters drivers that have not been fixed yet.
  63.      * @return ambiguity parameters drivers that have not been fixed yet
  64.      */
  65.     protected List<ParameterDriver> getFreeAmbiguityDrivers() {
  66.         return ambiguityDrivers.
  67.                         stream().
  68.                         filter(d -> {
  69.                             if (d.isSelected()) {
  70.                                 // in order to make the code generic and compatible with pDriver having
  71.                                 // 1 or several values driven getValue is called with a "random date"
  72.                                 // it should be OK as we take the near number
  73.                                 final double near   = FastMath.rint(d.getValue());
  74.                                 final double gapMin = near - d.getMinValue();
  75.                                 final double gapMax = d.getMaxValue() - near;
  76.                                 return FastMath.max(FastMath.abs(gapMin), FastMath.abs(gapMax)) > 1.0e-15;
  77.                             } else {
  78.                                 return false;
  79.                             }
  80.                         }).
  81.                         collect(Collectors.toList());
  82.     }

  83.     /** Get ambiguity indirection array for ambiguity parameters drivers that have not been fixed yet.
  84.      * @param startIndex start index for measurements parameters in global covariance matrix
  85.      * @param measurementsParametersDrivers measurements parameters drivers in global covariance matrix order
  86.      * @return indirection array between full covariance matrix and ambiguity covariance matrix
  87.      */
  88.     protected int[] getFreeAmbiguityIndirection(final int startIndex,
  89.                                                 final List<ParameterDriver> measurementsParametersDrivers) {

  90.         // set up indirection array
  91.         final List<ParameterDriver> freeDrivers = getFreeAmbiguityDrivers();
  92.         final List<String> measurementsPDriversNames = new ArrayList<>();
  93.         int totalValuesToEstimate = 0;
  94.         for (ParameterDriver driver : freeDrivers) {
  95.             totalValuesToEstimate += driver.getNbOfValues();
  96.         }
  97.         for (ParameterDriver measDriver : measurementsParametersDrivers) {
  98.             for (Span<String> spanMeasurementsParametersDrivers = measDriver.getNamesSpanMap().getFirstSpan();
  99.                     spanMeasurementsParametersDrivers != null; spanMeasurementsParametersDrivers = spanMeasurementsParametersDrivers.next()) {
  100.                 measurementsPDriversNames.add(spanMeasurementsParametersDrivers.getData());
  101.             }

  102.         }

  103.         final int[] indirection = new int[totalValuesToEstimate];
  104.         int nb = 0;
  105.         for (ParameterDriver freeDriver : freeDrivers) {

  106.             for (Span<String> spanFreeDriver = freeDriver.getNamesSpanMap().getFirstSpan(); spanFreeDriver != null; spanFreeDriver = spanFreeDriver.next()) {
  107.                 indirection[nb] = -1;

  108.                 for (int k = 0; k < measurementsPDriversNames.size(); ++k) {
  109.                     if (spanFreeDriver.getData().equals(measurementsPDriversNames.get(k))) {
  110.                         indirection[nb] = startIndex + k;
  111.                         break;
  112.                     }
  113.                 }

  114.                 if (indirection[nb] < 0) {
  115.                     // the parameter was not found
  116.                     final StringBuilder builder = new StringBuilder();
  117.                     for (final String driverName : measurementsPDriversNames) {
  118.                         if (builder.length() > 0) {
  119.                             builder.append(", ");
  120.                         }
  121.                         builder.append(driverName);
  122.                     }
  123.                     throw new OrekitIllegalArgumentException(OrekitMessages.UNSUPPORTED_PARAMETER_NAME,
  124.                             spanFreeDriver.getData(), builder.toString());
  125.                 }
  126.                 nb++;
  127.             }
  128.         }

  129.         return indirection;

  130.     }

  131.     /** Un-fix an integer ambiguity (typically after a phase cycle slip).
  132.      * @param ambiguityDriver driver for the ambiguity to un-fix
  133.      */
  134.     public void unFixAmbiguity(final ParameterDriver ambiguityDriver) {
  135.         ambiguityDriver.setMinValue(Double.NEGATIVE_INFINITY);
  136.         ambiguityDriver.setMaxValue(Double.POSITIVE_INFINITY);
  137.     }

  138.     /** Fix integer ambiguities.
  139.      * @param startIndex start index for measurements parameters in global covariance matrix
  140.      * @param measurementsParametersDrivers measurements parameters drivers in global covariance matrix order
  141.      * @param covariance global covariance matrix
  142.      * @return list of newly fixed ambiguities (ambiguities already fixed before the call are not counted)
  143.      */
  144.     public List<ParameterDriver> fixIntegerAmbiguities(final int startIndex,
  145.                                                        final List<ParameterDriver> measurementsParametersDrivers,
  146.                                                        final RealMatrix covariance) {

  147.         // set up Integer Least Square problem
  148.         final List<ParameterDriver> ambiguities      = getAllAmbiguityDrivers();

  149.         // construct floatambiguities array
  150.         int nbPDriver = 0;
  151.         for (ParameterDriver pDriver : ambiguities) {
  152.             nbPDriver += pDriver.getNbOfValues();
  153.         }
  154.         final double[]              floatAmbiguities = new double[nbPDriver];
  155.         int floatAmbRank = 0;
  156.         for (ParameterDriver pDriver : ambiguities) {
  157.             for (Span<Double> span = pDriver.getValueSpanMap().getFirstSpan(); span != null; span = span.next()) {
  158.                 floatAmbiguities[floatAmbRank++] = span.getData();
  159.             }
  160.         }

  161.         final int[]                 indirection      = getFreeAmbiguityIndirection(startIndex, measurementsParametersDrivers);
  162.         // solve the ILS problem
  163.         final IntegerLeastSquareSolution[] candidates =
  164.                         solver.solveILS(acceptance.numberOfCandidates(), floatAmbiguities, indirection, covariance);

  165.         // FIXME A cleaner way is:
  166.         //     1°/ Add a getName() method in IntegerLeastSquareSolver interface
  167.         //     2°/ Add static name attribute to IntegerBootstrapping, LAMBDA and ModifiedLAMBDA classes
  168.         if (solver instanceof IntegerBootstrapping && candidates.length == 0) {
  169.             return Collections.emptyList();
  170.         }

  171.         // check number of candidates
  172.         if (candidates.length < acceptance.numberOfCandidates()) {
  173.             return Collections.emptyList();
  174.         }

  175.         // check acceptance
  176.         final IntegerLeastSquareSolution bestCandidate = acceptance.accept(candidates);
  177.         if (bestCandidate == null) {
  178.             return Collections.emptyList();
  179.         }

  180.         // fix the ambiguities
  181.         final long[] fixedAmbiguities = bestCandidate.getSolution();
  182.         final List<ParameterDriver> fixedDrivers = new ArrayList<>(indirection.length);
  183.         int nb = 0;
  184.         for (int i = 0; i < measurementsParametersDrivers.size(); ++i) {
  185.             final ParameterDriver driver = measurementsParametersDrivers.get(indirection[nb] - startIndex);
  186.             driver.setMinValue(fixedAmbiguities[i]);
  187.             driver.setMaxValue(fixedAmbiguities[i]);
  188.             fixedDrivers.add(driver);
  189.             nb += driver.getNbOfValues();
  190.         }

  191.         // Update the others parameter drivers accordingly to the fixed integer ambiguity
  192.         // Covariance matrix between integer ambiguity and the other parameter driver
  193.         final RealMatrix Qab = getCovMatrix(covariance, indirection);

  194.         final RealVector X = new QRDecomposer(1.0e-10).decompose(getAmbiguityMatrix(covariance, indirection)).solve(MatrixUtils.createRealVector(floatAmbiguities).
  195.                                                                                                            subtract(MatrixUtils.createRealVector(toDoubleArray(fixedAmbiguities.length, fixedAmbiguities))));
  196.         final RealVector Y =  Qab.preMultiply(X);

  197.         int entry = 0;
  198.         for (int i = startIndex + 1; i < covariance.getColumnDimension(); i++) {
  199.             if (!belongTo(indirection, i)) {
  200.                 final ParameterDriver driver = measurementsParametersDrivers.get(i - startIndex);
  201.                 for (Span<Double> span = driver.getValueSpanMap().getFirstSpan(); span != null; span = span.next()) {

  202.                     driver.setValue(driver.getValue(span.getStart()) - Y.getEntry(entry++ - startIndex), span.getStart());
  203.                 }
  204.             }
  205.         }

  206.         return fixedDrivers;

  207.     }

  208.    /** Get the covariance matrix between the integer ambiguities and the other parameter driver.
  209.     * @param cov global covariance matrix
  210.     * @param indirection array of the position of integer ambiguity parameter driver
  211.     * @return covariance matrix.
  212.     */
  213.     private RealMatrix getCovMatrix(final RealMatrix cov, final int[] indirection) {
  214.         final RealMatrix Qab = MatrixUtils.createRealMatrix(indirection.length, cov.getColumnDimension());
  215.         int index = 0;
  216.         int iter  = 0;
  217.         while (iter < indirection.length) {
  218.             // Loop on column dimension
  219.             for (int j = 0; j < cov.getColumnDimension(); j++) {
  220.                 if (!belongTo(indirection, j)) {
  221.                     Qab.setEntry(index, 0, cov.getEntry(index, 0));
  222.                 }
  223.             }
  224.             index++;
  225.             iter++;
  226.         }
  227.         return Qab;
  228.     }

  229.      /** Return the matrix of the ambiguity from the global covariance matrix.
  230.       * @param cov global covariance matrix
  231.       * @param indirection array of the position of the ambiguity within the global covariance matrix
  232.       * @return matrix of ambiguities covariance
  233.       */
  234.     private RealMatrix getAmbiguityMatrix(final RealMatrix cov, final int[] indirection) {
  235.         final RealMatrix Qa = MatrixUtils.createRealMatrix(indirection.length, indirection.length);
  236.         for (int i = 0; i < indirection.length; i++) {
  237.             Qa.setEntry(i, i, cov.getEntry(indirection[i], indirection[i]));
  238.             for (int j = 0; j < i; j++) {
  239.                 Qa.setEntry(i, j, cov.getEntry(indirection[i], indirection[j]));
  240.                 Qa.setEntry(j, i, cov.getEntry(indirection[i], indirection[j]));
  241.             }
  242.         }
  243.         return Qa;
  244.     }

  245.     /** Compute whether or not the integer pos belongs to the indirection array.
  246.      * @param indirection array of the position of ambiguities within the global covariance matrix
  247.      * @param pos integer for which we want to know if it belong to the indirection array.
  248.      * @return true if it belongs.
  249.      */
  250.     private boolean belongTo(final int[] indirection, final int pos) {
  251.         for (int j : indirection) {
  252.             if (pos == j) {
  253.                 return true;
  254.             }
  255.         }
  256.         return false;
  257.     }

  258.     /** Transform an array of long to an array of double.
  259.      * @param size size of the destination array
  260.      * @param longArray source array
  261.      * @return the destination array
  262.      */
  263.     private double[] toDoubleArray(final int size, final long[] longArray) {
  264.         // Initialize double array
  265.         final double[] doubleArray = new double[size];
  266.         // Copy the elements
  267.         for (int index = 0; index < size; index++) {
  268.             doubleArray[index] = longArray[index];
  269.         }
  270.         return doubleArray;
  271.     }

  272. }