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

  20. import org.hipparchus.util.FastMath;

  21. /** Decorrelation/reduction engine for LAMBDA method.
  22.  * <p>
  23.  * This class implements PJG Teunissen Least Square Ambiguity Decorrelation
  24.  * Adjustment (LAMBDA) method, as described in both the 1996 paper <a
  25.  * 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.  * <p>
  33.  * It slightly departs on the original LAMBDA method as it does implement
  34.  * the following improvements proposed in the de Jonge and Tiberius 1996 paper
  35.  * that vastly speed up the search:
  36.  * </p>
  37.  * <ul>
  38.  *   <li>alternate search starting from the middle and expanding outwards</li>
  39.  *   <li>automatic shrinking of ellipsoid during the search</li>
  40.  * </ul>
  41.  * @see AmbiguitySolver
  42.  * @author Luc Maisonobe
  43.  * @since 10.0
  44.  */
  45. public class LambdaMethod extends AbstractLambdaMethod {

  46.     /** Margin factor to apply to estimated search limit parameter. */
  47.     private static final double CHI2_MARGIN_FACTOR = 1.1;

  48.     /** Empty constructor.
  49.      * <p>
  50.      * This constructor is not strictly necessary, but it prevents spurious
  51.      * javadoc warnings with JDK 18 and later.
  52.      * </p>
  53.      * @since 12.0
  54.      */
  55.     public LambdaMethod() {
  56.         // nothing to do
  57.     }

  58.     /** {@inheritDoc} */
  59.     @Override
  60.     protected void ltdlDecomposition() {

  61.         // get references to the diagonal and lower triangular parts
  62.         final double[] diag = getDiagReference();
  63.         final double[] low  = getLowReference();

  64.         // perform Lᵀ.D.L decomposition
  65.         for (int k = diag.length - 1; k >= 0; --k) {
  66.             final double inv = 1.0 / diag[k];
  67.             for (int i = 0; i < k; ++i) {
  68.                 final double lki = low[lIndex(k, i)];
  69.                 for (int j = 0; j < i; ++j) {
  70.                     low[lIndex(i, j)] -= lki * low[lIndex(k, j)] * inv;
  71.                 }
  72.                 diag[i] -= lki * lki * inv;
  73.             }
  74.             for (int j = 0; j < k; ++j) {
  75.                 low[lIndex(k, j)] *= inv;
  76.             }
  77.         }

  78.     }

  79.     /** {@inheritDoc} */
  80.     @Override
  81.     protected void reduction() {

  82.         // get references to the diagonal and lower triangular parts
  83.         final double[] diag = getDiagReference();
  84.         final double[] low  = getLowReference();
  85.         final int n = diag.length;

  86.         int kSaved = n - 2;
  87.         int k0 = kSaved;
  88.         while (k0 >= 0) {
  89.             final int k1 = k0 + 1;
  90.             if (k0 <= kSaved) {
  91.                 for (int i = k0 + 1; i < n; ++i) {
  92.                     integerGaussTransformation(i, k0);
  93.                 }
  94.             }
  95.             final double lk1k0 = low[lIndex(k1, k0)];
  96.             final double delta = diag[k0] + lk1k0 * lk1k0 * diag[k1];
  97.             if (delta < diag[k1]) {
  98.                 permutation(k0, delta);
  99.                 kSaved = k0;
  100.                 k0 = n - 2;
  101.             } else {
  102.                 k0--;
  103.             }
  104.         }
  105.     }

  106.     /** {@inheritDoc} */
  107.     @Override
  108.     protected void discreteSearch() {

  109.         // get references to the diagonal part
  110.         final double[] diag = getDiagReference();
  111.         final int n = diag.length;

  112.         // estimate search domain limit
  113.         final long[]   fixed   = new long[n];
  114.         final double[] offsets = new double[n];
  115.         final double[] left    = new double[n];
  116.         final double[] right   = new double[n];

  117.         final int maxSolutions = getMaxSolution();
  118.         final double[] decorrelated = getDecorrelatedReference();

  119.         final AlternatingSampler[] samplers = new AlternatingSampler[n];

  120.         // set up top level sampling for last ambiguity
  121.         double chi2 = estimateChi2(fixed, offsets);
  122.         right[n - 1] = chi2 / diag[n - 1];
  123.         int index = n - 1;
  124.         while (index < n) {
  125.             if (index == -1) {

  126.                 // all ambiguities have been fixed
  127.                 final double squaredNorm = chi2 - diag[0] * (right[0] - left[0]);
  128.                 addSolution(fixed, squaredNorm);
  129.                 final int size = getSolutionsSize();
  130.                 if (size >= maxSolutions) {

  131.                     // shrink the ellipsoid
  132.                     chi2 = squaredNorm;
  133.                     right[n - 1] = chi2 / diag[n - 1];
  134.                     for (int i = n - 1; i > 0; --i) {
  135.                         samplers[i].setRadius(FastMath.sqrt(right[i]));
  136.                         right[i - 1] = diag[i] / diag[i - 1] * (right[i] - left[i]);
  137.                     }
  138.                     samplers[0].setRadius(FastMath.sqrt(right[0]));

  139.                     if (size > maxSolutions) {
  140.                         removeSolution();
  141.                     }

  142.                 }

  143.                 ++index;

  144.             } else {

  145.                 if (samplers[index] == null) {
  146.                     // we start exploring a new ambiguity
  147.                     samplers[index] = new AlternatingSampler(conditionalEstimate(index, offsets), FastMath.sqrt(right[index]));
  148.                 } else {
  149.                     // continue exploring the same ambiguity
  150.                     samplers[index].generateNext();
  151.                 }

  152.                 if (samplers[index].inRange()) {
  153.                     fixed[index]       = samplers[index].getCurrent();
  154.                     offsets[index]     = fixed[index] - decorrelated[index];
  155.                     final double delta = fixed[index] - samplers[index].getMidPoint();
  156.                     left[index]        = delta * delta;
  157.                     if (index > 0) {
  158.                         right[index - 1]   = diag[index] / diag[index - 1] * (right[index] - left[index]);
  159.                     }

  160.                     // go down one level
  161.                     --index;

  162.                 } else {

  163.                     // we have completed exploration of this ambiguity range
  164.                     samplers[index] = null;

  165.                     // go up one level
  166.                     ++index;

  167.                 }

  168.             }
  169.         }

  170.     }

  171.     /** {@inheritDoc} */
  172.     @Override
  173.     protected void inverseDecomposition() {

  174.         // get references to the diagonal and lower triangular parts
  175.         final double[] diag = getDiagReference();
  176.         final double[] low  = getLowReference();
  177.         final int n = diag.length;

  178.         // we rely on the following equation, where a low triangular
  179.         // matrix L of dimension n is split into sub-matrices of dimensions
  180.         // k, l and m with k + l + m = n
  181.         //
  182.         // [  A  |      |    ]  [        A⁻¹        |         |       ]   [  Iₖ  |      |     ]
  183.         // [  B  |  Iₗ  |    ]  [       -BA⁻¹       |   Iₗ    |       ] = [      |  Iₗ  |     ]
  184.         // [  C  |  D   |  E ]  [ E⁻¹ (DB - C) A⁻¹  | -E⁻¹D   |  E⁻¹  ]   [      |      |  Iₘ ]
  185.         //
  186.         // considering we have already computed A⁻¹ (i.e. inverted rows 0 to k-1
  187.         // of L), and using l = 1 in the previous expression (i.e. the middle matrix
  188.         // is only one row), we see that elements 0 to k-1 of row k are given by -BA⁻¹
  189.         // and that element k is I₁ = 1. We can therefore invert L row by row and we
  190.         // obtain an inverse matrix L⁻¹ which is a low triangular matrix with unit
  191.         // diagonal. A⁻¹ is therefore also a low triangular matrix with unit diagonal.
  192.         // This property is used in the loops below to speed up the computation of -BA⁻¹
  193.         final double[] row = new double[n - 1];
  194.         diag[0] = 1.0 / diag[0];
  195.         for (int k = 1; k < n; ++k) {

  196.             // compute row k of lower triangular part, by computing -BA⁻¹
  197.             final int iK = lIndex(k, 0);
  198.             System.arraycopy(low, iK, row, 0, k);
  199.             for (int j = 0; j < k; ++j) {
  200.                 double sum = row[j];
  201.                 for (int l = j + 1; l < k; ++l) {
  202.                     sum += row[l] * low[lIndex(l, j)];
  203.                 }
  204.                 low[iK + j] = -sum;
  205.             }

  206.             // diagonal part
  207.             diag[k] = 1.0 / diag[k];

  208.         }

  209.     }

  210.     /** Compute a safe estimate of search limit parameter χ².
  211.      * <p>
  212.      * The estimate is based on section 4.11 in de Jonge and Tiberius 1996,
  213.      * computing χ² such that it includes at least a few preset grid points
  214.      * </p>
  215.      * @param fixed placeholder for test fixed ambiguities
  216.      * @param offsets placeholder for offsets between fixed ambiguities and float ambiguities
  217.      * @return safe estimate of search limit parameter χ²
  218.      */
  219.     private double estimateChi2(final long[] fixed, final double[] offsets) {

  220.         // get references to the diagonal part
  221.         final double[] diag = getDiagReference();
  222.         final int n = diag.length;
  223.         // maximum number of solutions seeked
  224.         final int maxSolutions = getMaxSolution();
  225.         // get references to the decorrelated ambiguities
  226.         final double[] decorrelated = getDecorrelatedReference();


  227.         // initialize test points, assuming ambiguities have been fully decorrelated
  228.         final AlternatingSampler[] samplers = new AlternatingSampler[n];
  229.         for (int i = 0; i < n; ++i) {
  230.             samplers[i] = new AlternatingSampler(decorrelated[i], 0.0);
  231.         }

  232.         final SortedSet<Double> squaredNorms = new TreeSet<>();

  233.         // first test point at center
  234.         for (int i = 0; i < n; ++i) {
  235.             fixed[i] = samplers[i].getCurrent();
  236.         }
  237.         squaredNorms.add(squaredNorm(fixed, offsets));

  238.         while (squaredNorms.size() < maxSolutions) {
  239.             // add a series of grid points, each shifted from center along a different axis
  240.             final int remaining = FastMath.min(n, maxSolutions - squaredNorms.size());
  241.             for (int i = 0; i < remaining; ++i) {
  242.                 final long saved = fixed[i];
  243.                 samplers[i].generateNext();
  244.                 fixed[i] = samplers[i].getCurrent();
  245.                 squaredNorms.add(squaredNorm(fixed, offsets));
  246.                 fixed[i] = saved;
  247.             }
  248.         }

  249.         // select a limit ensuring at least the needed number of grid points are in the search domain
  250.         int count = 0;
  251.         for (final double s : squaredNorms) {
  252.             if (++count == maxSolutions) {
  253.                 return s * CHI2_MARGIN_FACTOR;
  254.             }
  255.         }

  256.         // never reached
  257.         return Double.NaN;

  258.     }

  259.     /** Compute squared norm of a set of fixed ambiguities.
  260.      * @param fixed fixed ambiguities
  261.      * @param offsets placeholder for offsets between fixed ambiguities and float ambiguities
  262.      * @return squared norm of a set of fixed ambiguities
  263.      */
  264.     private double squaredNorm(final long[] fixed, final double[] offsets) {
  265.         // get references to the lower triangular part and the decorrelated ambiguities
  266.         final double[] diag = getDiagReference();
  267.         final double[] decorrelated = getDecorrelatedReference();
  268.         double n2 = 0;
  269.         for (int i = diag.length - 1; i >= 0; --i) {
  270.             offsets[i] = fixed[i] - decorrelated[i];
  271.             final double delta = fixed[i] - conditionalEstimate(i, offsets);
  272.             n2 += diag[i] * delta * delta;
  273.         }
  274.         return n2;
  275.     }

  276.     /** Compute conditional estimate of an ambiguity.
  277.      * <p>
  278.      * This corresponds to equation 4.4 in de Jonge and Tiberius 1996
  279.      * </p>
  280.      * @param i index of the ambiguity
  281.      * @param offsets offsets between already fixed ambiguities and float ambiguities
  282.      * @return conditional estimate of ambiguity â<sub>i|i+1...n</sub>
  283.      */
  284.     private double conditionalEstimate(final int i, final double[] offsets) {
  285.         // get references to the diagonal and lower triangular parts
  286.         final double[] diag = getDiagReference();
  287.         final double[] low  = getLowReference();
  288.         // get references to the decorrelated ambiguities
  289.         final double[] decorrelated = getDecorrelatedReference();

  290.         double conditional = decorrelated[i];
  291.         for (int j = i + 1; j < diag.length; ++j) {
  292.             conditional -= low[lIndex(j, i)] * offsets[j];
  293.         }
  294.         return conditional;
  295.     }

  296. }