LambdaMethod.java

  1. /* Copyright 2002-2022 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.     /** {@inheritDoc} */
  49.     @Override
  50.     protected void ltdlDecomposition() {

  51.         // get references to the diagonal and lower triangular parts
  52.         final double[] diag = getDiagReference();
  53.         final double[] low  = getLowReference();

  54.         // perform Lᵀ.D.L decomposition
  55.         for (int k = diag.length - 1; k >= 0; --k) {
  56.             final double inv = 1.0 / diag[k];
  57.             for (int i = 0; i < k; ++i) {
  58.                 final double lki = low[lIndex(k, i)];
  59.                 for (int j = 0; j < i; ++j) {
  60.                     low[lIndex(i, j)] -= lki * low[lIndex(k, j)] * inv;
  61.                 }
  62.                 diag[i] -= lki * lki * inv;
  63.             }
  64.             for (int j = 0; j < k; ++j) {
  65.                 low[lIndex(k, j)] *= inv;
  66.             }
  67.         }

  68.     }

  69.     /** {@inheritDoc} */
  70.     @Override
  71.     protected void reduction() {

  72.         // get references to the diagonal and lower triangular parts
  73.         final double[] diag = getDiagReference();
  74.         final double[] low  = getLowReference();
  75.         final int n = diag.length;

  76.         int kSaved = n - 2;
  77.         int k0 = kSaved;
  78.         while (k0 >= 0) {
  79.             final int k1 = k0 + 1;
  80.             if (k0 <= kSaved) {
  81.                 for (int i = k0 + 1; i < n; ++i) {
  82.                     integerGaussTransformation(i, k0);
  83.                 }
  84.             }
  85.             final double lk1k0 = low[lIndex(k1, k0)];
  86.             final double delta = diag[k0] + lk1k0 * lk1k0 * diag[k1];
  87.             if (delta < diag[k1]) {
  88.                 permutation(k0, delta);
  89.                 kSaved = k0;
  90.                 k0 = n - 2;
  91.             } else {
  92.                 k0--;
  93.             }
  94.         }
  95.     }

  96.     /** {@inheritDoc} */
  97.     @Override
  98.     protected void discreteSearch() {

  99.         // get references to the diagonal part
  100.         final double[] diag = getDiagReference();
  101.         final int n = diag.length;

  102.         // estimate search domain limit
  103.         final long[]   fixed   = new long[n];
  104.         final double[] offsets = new double[n];
  105.         final double[] left    = new double[n];
  106.         final double[] right   = new double[n];

  107.         final int maxSolutions = getMaxSolution();
  108.         final double[] decorrelated = getDecorrelatedReference();

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

  110.         // set up top level sampling for last ambiguity
  111.         double chi2 = estimateChi2(fixed, offsets);
  112.         right[n - 1] = chi2 / diag[n - 1];
  113.         int index = n - 1;
  114.         while (index < n) {
  115.             if (index == -1) {

  116.                 // all ambiguities have been fixed
  117.                 final double squaredNorm = chi2 - diag[0] * (right[0] - left[0]);
  118.                 addSolution(fixed, squaredNorm);
  119.                 final int size = getSolutionsSize();
  120.                 if (size >= maxSolutions) {

  121.                     // shrink the ellipsoid
  122.                     chi2 = squaredNorm;
  123.                     right[n - 1] = chi2 / diag[n - 1];
  124.                     for (int i = n - 1; i > 0; --i) {
  125.                         samplers[i].setRadius(FastMath.sqrt(right[i]));
  126.                         right[i - 1] = diag[i] / diag[i - 1] * (right[i] - left[i]);
  127.                     }
  128.                     samplers[0].setRadius(FastMath.sqrt(right[0]));

  129.                     if (size > maxSolutions) {
  130.                         removeSolution();
  131.                     }

  132.                 }

  133.                 ++index;

  134.             } else {

  135.                 if (samplers[index] == null) {
  136.                     // we start exploring a new ambiguity
  137.                     samplers[index] = new AlternatingSampler(conditionalEstimate(index, offsets), FastMath.sqrt(right[index]));
  138.                 } else {
  139.                     // continue exploring the same ambiguity
  140.                     samplers[index].generateNext();
  141.                 }

  142.                 if (samplers[index].inRange()) {
  143.                     fixed[index]       = samplers[index].getCurrent();
  144.                     offsets[index]     = fixed[index] - decorrelated[index];
  145.                     final double delta = fixed[index] - samplers[index].getMidPoint();
  146.                     left[index]        = delta * delta;
  147.                     if (index > 0) {
  148.                         right[index - 1]   = diag[index] / diag[index - 1] * (right[index] - left[index]);
  149.                     }

  150.                     // go down one level
  151.                     --index;

  152.                 } else {

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

  155.                     // go up one level
  156.                     ++index;

  157.                 }

  158.             }
  159.         }

  160.     }

  161.     /** {@inheritDoc} */
  162.     @Override
  163.     protected void inverseDecomposition() {

  164.         // get references to the diagonal and lower triangular parts
  165.         final double[] diag = getDiagReference();
  166.         final double[] low  = getLowReference();
  167.         final int n = diag.length;

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

  186.             // compute row k of lower triangular part, by computing -BA⁻¹
  187.             final int iK = lIndex(k, 0);
  188.             System.arraycopy(low, iK, row, 0, k);
  189.             for (int j = 0; j < k; ++j) {
  190.                 double sum = row[j];
  191.                 for (int l = j + 1; l < k; ++l) {
  192.                     sum += row[l] * low[lIndex(l, j)];
  193.                 }
  194.                 low[iK + j] = -sum;
  195.             }

  196.             // diagonal part
  197.             diag[k] = 1.0 / diag[k];

  198.         }

  199.     }

  200.     /** Compute a safe estimate of search limit parameter χ².
  201.      * <p>
  202.      * The estimate is based on section 4.11 in de Jonge and Tiberius 1996,
  203.      * computing χ² such that it includes at least a few preset grid points
  204.      * </p>
  205.      * @param fixed placeholder for test fixed ambiguities
  206.      * @param offsets placeholder for offsets between fixed ambiguities and float ambiguities
  207.      * @return safe estimate of search limit parameter χ²
  208.      */
  209.     private double estimateChi2(final long[] fixed, final double[] offsets) {

  210.         // get references to the diagonal part
  211.         final double[] diag = getDiagReference();
  212.         final int n = diag.length;
  213.         // maximum number of solutions seeked
  214.         final int maxSolutions = getMaxSolution();
  215.         // get references to the decorrelated ambiguities
  216.         final double[] decorrelated = getDecorrelatedReference();


  217.         // initialize test points, assuming ambiguities have been fully decorrelated
  218.         final AlternatingSampler[] samplers = new AlternatingSampler[n];
  219.         for (int i = 0; i < n; ++i) {
  220.             samplers[i] = new AlternatingSampler(decorrelated[i], 0.0);
  221.         }

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

  223.         // first test point at center
  224.         for (int i = 0; i < n; ++i) {
  225.             fixed[i] = samplers[i].getCurrent();
  226.         }
  227.         squaredNorms.add(squaredNorm(fixed, offsets));

  228.         while (squaredNorms.size() < maxSolutions) {
  229.             // add a series of grid points, each shifted from center along a different axis
  230.             final int remaining = FastMath.min(n, maxSolutions - squaredNorms.size());
  231.             for (int i = 0; i < remaining; ++i) {
  232.                 final long saved = fixed[i];
  233.                 samplers[i].generateNext();
  234.                 fixed[i] = samplers[i].getCurrent();
  235.                 squaredNorms.add(squaredNorm(fixed, offsets));
  236.                 fixed[i] = saved;
  237.             }
  238.         }

  239.         // select a limit ensuring at least the needed number of grid points are in the search domain
  240.         int count = 0;
  241.         for (final double s : squaredNorms) {
  242.             if (++count == maxSolutions) {
  243.                 return s * CHI2_MARGIN_FACTOR;
  244.             }
  245.         }

  246.         // never reached
  247.         return Double.NaN;

  248.     }

  249.     /** Compute squared norm of a set of fixed ambiguities.
  250.      * @param fixed fixed ambiguities
  251.      * @param offsets placeholder for offsets between fixed ambiguities and float ambiguities
  252.      * @return squared norm of a set of fixed ambiguities
  253.      */
  254.     private double squaredNorm(final long[] fixed, final double[] offsets) {
  255.         // get references to the lower triangular part and the decorrelated ambiguities
  256.         final double[] diag = getDiagReference();
  257.         final double[] decorrelated = getDecorrelatedReference();
  258.         double n2 = 0;
  259.         for (int i = diag.length - 1; i >= 0; --i) {
  260.             offsets[i] = fixed[i] - decorrelated[i];
  261.             final double delta = fixed[i] - conditionalEstimate(i, offsets);
  262.             n2 += diag[i] * delta * delta;
  263.         }
  264.         return n2;
  265.     }

  266.     /** Compute conditional estimate of an ambiguity.
  267.      * <p>
  268.      * This corresponds to equation 4.4 in de Jonge and Tiberius 1996
  269.      * </p>
  270.      * @param i index of the ambiguity
  271.      * @param offsets offsets between already fixed ambiguities and float ambiguities
  272.      * @return conditional estimate of ambiguity â<sub>i|i+1...n</sub>
  273.      */
  274.     private double conditionalEstimate(final int i, final double[] offsets) {
  275.         // get references to the diagonal and lower triangular parts
  276.         final double[] diag = getDiagReference();
  277.         final double[] low  = getLowReference();
  278.         // get references to the decorrelated ambiguities
  279.         final double[] decorrelated = getDecorrelatedReference();

  280.         double conditional = decorrelated[i];
  281.         for (int j = i + 1; j < diag.length; ++j) {
  282.             conditional -= low[lIndex(j, i)] * offsets[j];
  283.         }
  284.         return conditional;
  285.     }

  286. }