AbstractLambdaMethod.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.Comparator;
  19. import java.util.SortedSet;
  20. import java.util.TreeSet;

  21. import org.hipparchus.linear.RealMatrix;
  22. import org.hipparchus.util.FastMath;

  23. /** Base class for decorrelation/reduction engine for LAMBDA type methods.
  24.  * <p>
  25.  * This class is based on both the 1996 paper <a href="https://www.researchgate.net/publication/2790708_The_LAMBDA_method_for_integer_ambiguity_estimation_implementation_aspects">
  26.  * The LAMBDA method for integer ambiguity estimation: implementation aspects</a> by
  27.  * Paul de Jonge and Christian Tiberius and on the 2005 paper <a
  28.  * href="https://www.researchgate.net/publication/225518977_MLAMBDA_a_modified_LAMBDA_method_for_integer_least-squares_estimation">
  29.  * A modified LAMBDA method for integer least-squares estimation</a> by X.-W Chang, X. Yang
  30.  * and T. Zhou, Journal of Geodesy 79(9):552-565, DOI: 10.1007/s00190-005-0004-x
  31.  * </p>
  32.  * @author Luc Maisonobe
  33.  * @since 10.0
  34.  */
  35. public abstract class AbstractLambdaMethod implements IntegerLeastSquareSolver {

  36.     /** Number of ambiguities. */
  37.     private int n;

  38.     /** Decorrelated ambiguities. */
  39.     private double[] decorrelated;

  40.     /** Lower triangular matrix with unit diagonal, in row order (unit diagonal not stored). */
  41.     private double[] low;

  42.     /** Diagonal matrix. */
  43.     private double[] diag;

  44.     /** Z⁻¹ transformation matrix, in row order. */
  45.     private int[] zInverseTransformation;

  46.     /** Maximum number of solutions seeked. */
  47.     private int maxSolutions;

  48.     /** Placeholder for solutions found. */
  49.     private SortedSet<IntegerLeastSquareSolution> solutions;

  50.     /** Comparator for integer least square solutions. */
  51.     private Comparator<IntegerLeastSquareSolution> comparator;

  52.     /** Constructor.
  53.      * <p>
  54.      * By default a {@link IntegerLeastSquareComparator} is used
  55.      * to compare integer least square solutions
  56.      * </p>
  57.      */
  58.     protected AbstractLambdaMethod() {
  59.         this.comparator = new IntegerLeastSquareComparator();
  60.     }

  61.     /** {@inheritDoc} */
  62.     @Override
  63.     public IntegerLeastSquareSolution[] solveILS(final int nbSol, final double[] floatAmbiguities,
  64.                                                  final int[] indirection, final RealMatrix covariance) {

  65.         // initialize the ILS problem search
  66.         initializeProblem(floatAmbiguities, indirection, covariance, nbSol);

  67.         // perform initial Lᵀ.D.L = Q decomposition of covariance
  68.         ltdlDecomposition();

  69.         // perform decorrelation/reduction of covariances
  70.         reduction();

  71.         // transform the Lᵀ.D.L = Q decomposition of covariance into
  72.         // the L⁻¹.D⁻¹.L⁻ᵀ = Q⁻¹ decomposition of the inverse of covariance.
  73.         inverseDecomposition();

  74.         // perform discrete search of Integer Least Square problem
  75.         discreteSearch();

  76.         return recoverAmbiguities();

  77.     }

  78.     /** Set a custom comparator for integer least square solutions comparison.
  79.      * <p>
  80.      * Calling this method overrides any comparator that could have been set
  81.      * beforehand. It also overrides the default {@link IntegerLeastSquareComparator}.
  82.      * </p>
  83.      * @param newCompartor new comparator to use
  84.      * @since 11.0
  85.      */
  86.     public void setComparator(final Comparator<IntegerLeastSquareSolution> newCompartor) {
  87.         this.comparator = newCompartor;
  88.     }

  89.     /** Initialize ILS problem.
  90.      * @param floatAmbiguities float estimates of ambiguities
  91.      * @param indirection indirection array to extract ambiguity covariances from global covariance matrix
  92.      * @param globalCovariance global covariance matrix (includes ambiguities among other parameters)
  93.      * @param nbSol number of solutions to search for
  94.      */
  95.     private void initializeProblem(final double[] floatAmbiguities, final int[] indirection,
  96.                                    final RealMatrix globalCovariance, final int nbSol) {

  97.         this.n                      = floatAmbiguities.length;
  98.         this.decorrelated           = floatAmbiguities.clone();
  99.         this.low                    = new double[(n * (n - 1)) / 2];
  100.         this.diag                   = new double[n];
  101.         this.zInverseTransformation = new int[n * n];
  102.         this.maxSolutions           = nbSol;
  103.         this.solutions              = new TreeSet<>(comparator);

  104.         // initialize decomposition matrices
  105.         for (int i = 0; i < n; ++i) {
  106.             for (int j = 0; j < i; ++j) {
  107.                 low[lIndex(i, j)] = globalCovariance.getEntry(indirection[i], indirection[j]);
  108.             }
  109.             diag[i] = globalCovariance.getEntry(indirection[i], indirection[i]);
  110.             zInverseTransformation[zIndex(i, i)] = 1;
  111.         }

  112.     }

  113.     /** Get a reference to the diagonal matrix of the decomposition.
  114.      * <p>
  115.      * BEWARE: the returned value is a reference to an internal array,
  116.      * it is <em>only</em> intended for subclasses use (hence the
  117.      * method is protected and not public).
  118.      * </p>
  119.      * @return reference to the diagonal matrix of the decomposition
  120.      */
  121.     protected double[] getDiagReference() {
  122.         return diag;
  123.     }

  124.     /** Get a reference to the lower triangular matrix of the decomposition.
  125.      * <p>
  126.      * BEWARE: the returned value is a reference to an internal array,
  127.      * it is <em>only</em> intended for subclasses use (hence the
  128.      * method is protected and not public).
  129.      * </p>
  130.      * @return reference to the lower triangular matrix of the decomposition
  131.      */
  132.     protected double[] getLowReference() {
  133.         return low;
  134.     }

  135.     /** Get the reference decorrelated ambiguities.
  136.      * @return reference to the decorrelated ambiguities.
  137.      **/
  138.     protected double[] getDecorrelatedReference() {
  139.         return decorrelated;
  140.     }

  141.     /** Get the maximum number of solutions seeked.
  142.      * @return the maximum number of solutions seeked
  143.      */
  144.     protected int getMaxSolution() {
  145.         return maxSolutions;
  146.     }

  147.     /** Add a new solution.
  148.      * @param fixed solution array
  149.      * @param squaredNorm squared distance to the corresponding float solution
  150.      */
  151.     protected void addSolution(final long[] fixed, final double squaredNorm) {
  152.         solutions.add(new IntegerLeastSquareSolution(fixed, squaredNorm));
  153.     }

  154.     /** Remove spurious solution.
  155.      */
  156.     protected void removeSolution() {
  157.         solutions.remove(solutions.last());
  158.     }

  159.     /** Get the number of solutions found.
  160.      * @return the number of solutions found
  161.      */
  162.     protected int getSolutionsSize() {
  163.         return solutions.size();
  164.     }

  165.     /**Get the maximum of distance among the solutions found.
  166.      * getting last of solutions as they are sorted in SortesSet
  167.      * @return greatest distance of the solutions
  168.      * @since 10.2
  169.      */
  170.     protected double getMaxDistance() {
  171.         return solutions.last().getSquaredDistance();
  172.     }

  173.     /** Get a reference to the Z  inverse transformation matrix.
  174.      * <p>
  175.      * BEWARE: the returned value is a reference to an internal array,
  176.      * it is <em>only</em> intended for subclasses use (hence the
  177.      * method is protected and not public).
  178.      * BEWARE: for the MODIFIED LAMBDA METHOD, the returned matrix Z is such that
  179.      * Q = Z'L'DLZ where Q is the covariance matrix and ' refers to the transposition operation
  180.      * @return array of integer corresponding to Z matrix
  181.      * @since 10.2
  182.      */
  183.     protected int[] getZInverseTransformationReference() {
  184.         return zInverseTransformation;
  185.     }

  186.     /** Get the size of the problem. In the ILS problem, the integer returned
  187.      * is the size of the covariance matrix.
  188.      * @return the size of the ILS problem
  189.      * @since 10.2
  190.      */
  191.     protected int getSize() {
  192.         return n;
  193.     }

  194.     /** Perform Lᵀ.D.L = Q decomposition of the covariance matrix.
  195.      */
  196.     protected abstract void ltdlDecomposition();

  197.     /** Perform LAMBDA reduction.
  198.      */
  199.     protected abstract void reduction();

  200.     /** Find the best solutions to the Integer Least Square problem.
  201.      */
  202.     protected abstract void discreteSearch();

  203.     /** Inverse the decomposition.
  204.      * <p>
  205.      * This method transforms the Lᵀ.D.L = Q decomposition of covariance into
  206.      * the L⁻¹.D⁻¹.L⁻ᵀ = Q⁻¹ decomposition of the inverse of covariance.
  207.      * </p>
  208.      */
  209.     protected abstract void inverseDecomposition();

  210.     /** Perform one integer Gauss transformation.
  211.      * <p>
  212.      * This method corresponds to algorithm 2.1 in X.-W Chang, X. Yang and T. Zhou paper.
  213.      * </p>
  214.      * @param row row index (counting from 0)
  215.      * @param col column index (counting from 0)
  216.      */
  217.     protected void integerGaussTransformation(final int row, final int col) {
  218.         final int mu = (int) FastMath.rint(low[lIndex(row, col)]);
  219.         if (mu != 0) {

  220.             // update low triangular matrix (post-multiplying L by Zᵢⱼ = I - μ eᵢ eⱼᵀ)
  221.             low[lIndex(row, col)] -= mu;
  222.             for (int i = row + 1; i < n; ++i) {
  223.                 low[lIndex(i, col)] -= mu * low[lIndex(i, row)];
  224.             }

  225.             // update Z⁻¹ transformation matrix
  226.             for (int i = 0; i < n; ++i) {
  227.                 // pre-multiplying Z⁻¹ by Zᵢⱼ⁻¹ = I + μ eᵢ eⱼᵀ
  228.                 zInverseTransformation[zIndex(row, i)] += mu * zInverseTransformation[zIndex(col, i)];
  229.             }

  230.             // update decorrelated ambiguities estimates (pre-multiplying a by  Zᵢⱼᵀ = I - μ eⱼ eᵢᵀ)
  231.             decorrelated[col] -= mu * decorrelated[row];

  232.         }
  233.     }

  234.     /** Perform one symmetric permutation involving rows/columns {@code k0} and {@code k0+1}.
  235.      * <p>
  236.      * This method corresponds to algorithm 2.2 in X.-W Chang, X. Yang and T. Zhou paper.
  237.      * </p>
  238.      * @param k0 diagonal index (counting from 0)
  239.      * @param delta new value for diag[k0+1]
  240.      */
  241.     protected void permutation(final int k0, final double delta) {

  242.         final int    k1        = k0 + 1;
  243.         final int    k2        = k0 + 2;
  244.         final int    indexk1k0 = lIndex(k1, k0);
  245.         final double lk1k0     = low[indexk1k0];
  246.         final double eta       = diag[k0] / delta;
  247.         final double lambda    = diag[k1] * lk1k0 / delta;

  248.         // update diagonal
  249.         diag[k0] = eta * diag[k1];
  250.         diag[k1] = delta;

  251.         // update low triangular matrix
  252.         for (int j = 0; j < k0; ++j) {
  253.             final int indexk0j = lIndex(k0, j);
  254.             final int indexk1j = lIndex(k1, j);
  255.             final double lk0j  = low[indexk0j];
  256.             final double lk1j  = low[indexk1j];
  257.             low[indexk0j]      = lk1j          - lk1k0 * lk0j;
  258.             low[indexk1j]      = lambda * lk1j + eta   * lk0j;
  259.         }
  260.         low[indexk1k0] = lambda;
  261.         for (int i = k2; i < n; ++i) {
  262.             final int indexik0 = lIndex(i, k0);
  263.             final int indexik1 = indexik0 + 1;
  264.             final double tmp   = low[indexik0];
  265.             low[indexik0]      = low[indexik1];
  266.             low[indexik1]      = tmp;
  267.         }

  268.         // update Z⁻¹ transformation matrix
  269.         for (int i = 0; i < n; ++i) {

  270.             final int indexk0i               = zIndex(k0, i);
  271.             final int indexk1i               = indexk0i + n;
  272.             final int tmp2                   = zInverseTransformation[indexk0i];
  273.             zInverseTransformation[indexk0i] = zInverseTransformation[indexk1i];
  274.             zInverseTransformation[indexk1i] = tmp2;

  275.         }

  276.         // update decorrelated ambiguities
  277.         final double tmp = decorrelated[k0];
  278.         decorrelated[k0] = decorrelated[k1];
  279.         decorrelated[k1] = tmp;

  280.     }

  281.     /** Recover ambiguities prior to the Z-transformation.
  282.      * @return recovered ambiguities
  283.      */
  284.     protected IntegerLeastSquareSolution[] recoverAmbiguities() {

  285.         final IntegerLeastSquareSolution[] recovered = new IntegerLeastSquareSolution[solutions.size()];

  286.         int k = 0;
  287.         final long[] a = new long[n];
  288.         for (final IntegerLeastSquareSolution solution : solutions) {
  289.             final long[] s = solution.getSolution();
  290.             for (int i = 0; i < n; ++i) {
  291.                 // compute a = Z⁻ᵀ.s
  292.                 long ai = 0;
  293.                 int l = zIndex(0, i);
  294.                 for (int j = 0; j < n; ++j) {
  295.                     ai += zInverseTransformation[l] * s[j];
  296.                     l  += n;
  297.                 }
  298.                 a[i] = ai;
  299.             }
  300.             recovered[k++] = new IntegerLeastSquareSolution(a, solution.getSquaredDistance());
  301.         }

  302.         return recovered;

  303.     }

  304.     /** Get the index of an entry in the lower triangular matrix.
  305.      * @param row row index (counting from 0)
  306.      * @param col column index (counting from 0)
  307.      * @return index in the single dimension array
  308.      */
  309.     protected int lIndex(final int row, final int col) {
  310.         return (row * (row - 1)) / 2 + col;
  311.     }

  312.     /** Get the index of an entry in the Z transformation matrix.
  313.      * @param row row index (counting from 0)
  314.      * @param col column index (counting from 0)
  315.      * @return index in the single dimension array
  316.      */
  317.     protected int zIndex(final int row, final int col) {
  318.         return row * n + col;
  319.     }

  320. }