ModifiedLambdaMethod.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 org.hipparchus.util.FastMath;

  19. /** Decorrelation/reduction engine for Modified LAMBDA method.
  20.  * <p>
  21.  * This class implements Modified Least Square Ambiguity Decorrelation
  22.  * Adjustment (MLAMBDA) method, as described in <a
  23.  * href="https://www.researchgate.net/publication/225518977_MLAMBDA_a_modified_LAMBDA_method_for_integer_least-squares_estimation">
  24.  * A modified LAMBDA method for integer least-squares estimation</a> by X.-W Chang, X. Yang
  25.  * and T. Zhou, Journal of Geodesy 79(9):552-565, DOI: 10.1007/s00190-005-0004-x
  26.  * </p>
  27.  *
  28.  * @see AmbiguitySolver
  29.  * @author David Soulard
  30.  * @since 10.2
  31.  */
  32. public class ModifiedLambdaMethod extends AbstractLambdaMethod {

  33.     /** Empty constructor.
  34.      * <p>
  35.      * This constructor is not strictly necessary, but it prevents spurious
  36.      * javadoc warnings with JDK 18 and later.
  37.      * </p>
  38.      * @since 12.0
  39.      */
  40.     public ModifiedLambdaMethod() {
  41.         // nothing to do
  42.     }

  43.     /** Compute the LᵀDL factorization with symmetric pivoting decomposition of Q
  44.      * (symmetric definite positive matrix) with a minimum symmetric pivoting: Q = ZᵀLᵀDLZ.
  45.      */
  46.     @Override
  47.     protected void ltdlDecomposition() {
  48.         final double[] diag = getDiagReference();
  49.         final double[] low  = getLowReference();
  50.         final int[] Z = getZInverseTransformationReference();
  51.         final double[] aNew = getDecorrelatedReference();
  52.         for (int  i = 0; i < this.getSize(); i++) {
  53.             Z[zIndex(i, i)] = 0;
  54.         }
  55.         final int n = diag.length;
  56.         final int[] perm = new int[n];
  57.         for (int i = 0; i < n; i++) {
  58.             perm[i] = i;
  59.         }
  60.         for (int k = n - 1; k >= 0; k--) {
  61.             final int q = posMin(diag, k);
  62.             exchangeIntP1WithIntP2(perm, k, q);
  63.             permRowThenCol(low, diag, k, q);
  64.             if (k > 0) {
  65.                 final double Wkk = diag[k];
  66.                 divide(low, Wkk, k);
  67.                 set(low, diag, k);
  68.             }
  69.             exchangeDoubleP1WithDoubleP2(aNew, k, q);
  70.         }
  71.         for (int j = 0; j < n; j++) {
  72.             Z[zIndex(j, perm[j])] = 1;
  73.         }
  74.     }

  75.     /** Perform MLAMBDA reduction.
  76.      */
  77.     @Override
  78.     protected void reduction() {
  79.         final double[] diag = getDiagReference();
  80.         final double[] low  = getLowReference();
  81.         final int n = diag.length;
  82.         int k = getSize() - 2;
  83.         while ( k > -1) {
  84.             final int kp1 = k + 1;
  85.             double tmp = low[lIndex(kp1, k)];
  86.             final double mu = FastMath.rint(tmp);
  87.             if (Math.abs(mu) > 1e-9) {
  88.                 tmp -= mu;
  89.             }
  90.             final double delta = diag[k] + tmp * tmp * diag[kp1];
  91.             if (delta < diag[kp1]) {
  92.                 integerGaussTransformation(kp1, k);
  93.                 if (mu > 0) {
  94.                     for (int i = k + 2; i < n; i++) {
  95.                         integerGaussTransformation(i, k);
  96.                     }
  97.                 }
  98.                 permutation(k, delta);
  99.                 if (k < n - 2) {
  100.                     k++;
  101.                 }
  102.             } else {
  103.                 k--;
  104.             }
  105.         }
  106.     }

  107.     /** {@inheritDoc} */
  108.     @Override
  109.     protected void discreteSearch() {
  110.         //initialization
  111.         final int n                 = getSize();
  112.         final int maxSolutions      = getMaxSolution();
  113.         final double[] diag         = getDiagReference();
  114.         final double[] decorrelated = getDecorrelatedReference();
  115.         final double[] low          = getLowReference();
  116.         final long[] z              = new long[n];
  117.         final double[] zb           = new double[n];
  118.         final double[] y            = new double[n];
  119.         final int[] path            = new int[n];

  120.         for (int i = 0; i < n; i++) {
  121.             path[i] = n - 1;
  122.         }

  123.         final double[] step    = new double[n];
  124.         final double[] dist    = new double[n + 1];
  125.         final double[] lS      = new double[(n * (n - 1)) / 2];
  126.         final double[] dS      = new double[n];
  127.         double maxDist         = Double.POSITIVE_INFINITY;
  128.         int count              = 0;
  129.         boolean endSearch      = false;
  130.         int ulevel             = 0;

  131.         // Determine which level to move to after z(0) is chosen at level 1.
  132.         final int k0;
  133.         if (maxSolutions == 1) {
  134.             k0 = 1;
  135.         }
  136.         else {
  137.             k0 = 0;
  138.         }

  139.         // Initialization at level n
  140.         zb[n - 1] = decorrelated.clone()[n - 1];
  141.         z[n -  1] = (long) FastMath.rint(zb[n - 1]);
  142.         y[n - 1] = zb[n - 1] - z[n - 1];
  143.         step[n - 1] =  sign(y[n - 1]);
  144.         int k = n - 1;
  145.         while (!endSearch) {
  146.             for (int i = ulevel; i <= k - 1; i++) {
  147.                 path[i] = k;
  148.             }
  149.             for (int j = ulevel - 1; j >= 0; j--) {
  150.                 if (path[j] < k) {
  151.                     path[j] = k;
  152.                 } else {
  153.                     break;
  154.                 }
  155.             }
  156.             double newDist = dist[k] + y[k] * y[k] / diag[k];
  157.             while (newDist < maxDist) {
  158.                 // move to level k-1
  159.                 if (k != 0) {
  160.                     k--;
  161.                     dist[k] = newDist;
  162.                     for (int j = path[k]; j > k; j--) {
  163.                         if (j - 1 == k) {
  164.                             //Update diagonal
  165.                             dS[k] = lS[lIndex(j, k)] - y[j] * low[lIndex(j, k)];
  166.                         } else {
  167.                             //Update low triangular part
  168.                             lS[lIndex(j - 1, k)] = lS[lIndex(j, k)] - y[j] * low[lIndex(j, k)];
  169.                         }
  170.                     }

  171.                     zb[k] = decorrelated[k] + dS[k];
  172.                     z[k] =  (long) FastMath.rint(zb[k]);
  173.                     y[k] = zb[k] - z[k];
  174.                     step[k] =  sign(y[k]);
  175.                 } else {
  176.                     //Save the value of one optimum z
  177.                     if (count < (maxSolutions - 1)) {
  178.                         addSolution(z, newDist);
  179.                         count++;
  180.                     //Save the value of one optimum z
  181.                     } else if (count == (maxSolutions - 1)) {
  182.                         addSolution(z, newDist);
  183.                         maxDist = getMaxDistance();
  184.                         count++;
  185.                     //Replace the new solution with the one with the greatest distance
  186.                     } else {
  187.                         removeSolution();
  188.                         addSolution(z, newDist);
  189.                         maxDist = getMaxDistance();
  190.                     }
  191.                     k = k0;
  192.                     z[k] = z[k] + (long) step[k];
  193.                     y[k] = zb[k] - z[k];
  194.                     step[k] = -step[k] -  sign(step[k]);
  195.                 }
  196.                 newDist = dist[k] + y[k] * y[k] / diag[k];
  197.             }
  198.             ulevel = k;
  199.             //exit or move to level k+1
  200.             while (newDist >= maxDist) {
  201.                 if (k == n - 1) {
  202.                     endSearch = true;
  203.                     break;
  204.                 }
  205.                 //move to level k+1
  206.                 k++;
  207.                 //next integer
  208.                 z[k] = z[k] + (long) step[k];
  209.                 y[k] = zb[k] - z[k];
  210.                 step[k] = -step[k] -  sign(step[k]);
  211.                 newDist = dist[k] + y[k] * y[k] / diag[k];
  212.             }
  213.         }
  214.     }

  215.     /** {@inheritDoc} */
  216.     @Override
  217.     protected void inverseDecomposition() {
  218.         // nothing for M-LAMBDA method
  219.     }

  220.     /** Return the position of the smallest element in the diagonal of a matrix given in parameter.
  221.      * @param k the position of the smallest diagonal element
  222.      * @param D an array of double being the diagonal of the covariance matrix
  223.      * @return the position of the smallest element of mat.
  224.      */
  225.     private int posMin(final double[] D, final int k) {
  226.         int q = 0;
  227.         double value = D[0];
  228.         for (int i = 1; i <= k; i++) {
  229.             if (value > D[i]) {
  230.                 value = D[i];
  231.                 q = i;
  232.             }
  233.         }
  234.         return q;
  235.     }

  236.     /** Perform the following operation on the matrix W in parameters.
  237.      * W(1:p-1,1:p-1) = W(1:p-1,1:p-1) - W(p,1:p-1)'*W(p,p)*W(p,1:p-1);
  238.      * @param L array of double being a lower triangular part of the covariance matrix
  239.      * @param D array of double being the diagonal of the covariance matrix
  240.      * @param p integer at which the computation is done
  241.      */
  242.     private void set(final double[] L, final double[] D, final int p) {
  243.         final double d = D[p];
  244.         final double[] row = new double[p];
  245.         for (int i = 0; i < p; i++) {
  246.             row[i] = L[lIndex(p, i)];
  247.         }
  248.         for (int i = 0; i < p; i++) {
  249.             for (int j = 0; j < i; j++) {
  250.                 L[lIndex(i, j)] = L[lIndex(i, j)] - row[i] * row[j] * d;
  251.             }
  252.             D[i] = D[i] - row[i] * row[i] * d;
  253.         }
  254.     }

  255.     /** Perform a permutation between two row and then two column of the covariance matrix.
  256.      * @param L array of double being the lower triangular part of the covariance matrix
  257.      * @param D array of double being the diagonal of the covariance matrix
  258.      * @param p1 integer: position of the permutation
  259.      * @param p2 integer: position of the permutation
  260.      */
  261.     private void permRowThenCol(final double[] L, final double[] D, final int p1, final int p2) {
  262.         final double[] rowP1 = getRow(L, D, p1);
  263.         final double[] rowP2 = getRow(L, D, p2);
  264.         if (p1 > p2) {
  265.             //Update row P2
  266.             for (int j = 0; j < p2; j++) {
  267.                 L[lIndex(p2, j)] = rowP1[j];
  268.             }
  269.             D[p2] = rowP1[p2];
  270.             //Update row p1
  271.             for (int j = 0; j < p1; j++) {
  272.                 L[lIndex(p1, j)] = rowP2[j];
  273.             }
  274.             D[p1] = rowP2[p1];
  275.             final double[] colP1 = getColPmax(L, D, rowP1, p2, p1);
  276.             //Update column P1
  277.             for (int i = p1 + 1; i < D.length; i++) {
  278.                 L[lIndex(i, p1)] = L[lIndex(i, p2)];
  279.             }
  280.             D[p1] = L[lIndex(p1, p2)];
  281.             //Update column P2
  282.             for (int i = p2 + 1; i < D.length; i++) {
  283.                 L[lIndex(i, p2)] = colP1[i];
  284.             }
  285.             D[p2] = colP1[p2];
  286.         } else {
  287.             //Does nothing when p1 <= p2
  288.         }
  289.     }

  290.     /**Get the row of the covariance matrix at the given position (count from 0).
  291.      * @param L lower part of the covariance matrix
  292.      * @param D diagonal part of the covariance matrix
  293.      * @param pos wanted position
  294.      * @return array of double being the row of covariance matrix at given position
  295.      */
  296.     private double[] getRow(final double[] L, final double[] D, final int pos) {
  297.         final double[] row = new double[D.length];
  298.         for (int j = 0; j < pos; j++) {
  299.             row[j] = L[lIndex(pos, j)];
  300.         }
  301.         row[pos] = D[pos];
  302.         for (int j = pos + 1; j < D.length; j++) {
  303.             row[j] = L[lIndex(j, pos)];
  304.         }
  305.         return row;
  306.     }

  307.     /**Getter for column the greatest at the right side.
  308.      * @param L double array being the lower triangular matrix
  309.      * @param D double array being the diagonal matrix
  310.      * @param row double array being the row of the matrix at given position
  311.      * @param pmin position at which we get the column
  312.      * @param pmax other positions
  313.      * @return array of double
  314.      */
  315.     private double[] getColPmax(final double[] L, final double[] D, final double[] row, final int pmin, final int pmax) {
  316.         final double[] col = new double[D.length];
  317.         col[pmin] = row[pmax];
  318.         for (int i = pmin + 1; i < pmax; i++) {
  319.             col[i] = row[i];
  320.         }
  321.         col[pmax] = D[pmax];
  322.         for (int i = pmax + 1; i < D.length; i++) {
  323.             col[i] = L[lIndex(i, pmax)];
  324.         }
  325.         return col;
  326.     }

  327.     /** Perform the following operation:  W(k,1:k-1) = W(k,1:k-1)/W(k,k) where W is the covariance matrix.
  328.      * @param mat lower triangular part of the covaraince matrix
  329.      * @param diag double: value of the covaraicne matrix at psoition (k;k)
  330.      * @param k integer needed
  331.      */
  332.     private void divide(final double[] mat, final double diag, final int k) {
  333.         for (int j = 0; j < k; j++) {
  334.             mat[lIndex(k, j)] = mat[lIndex(k, j)] / diag;
  335.         }
  336.     }

  337.     /**Inverse the position of 2 element of the array in parameters.
  338.      * @param mat array on which change should be done
  339.      * @param p1 position of the first element to change
  340.      * @param p2 position of the second element to change
  341.      * @return
  342.      */
  343.     private void  exchangeIntP1WithIntP2(final int[] mat, final int p1, final int p2) {
  344.         final int[] Z = mat.clone();
  345.         mat[p1] = Z[p2];
  346.         mat[p2] = Z[p1];
  347.     }

  348.     /** On the array of double mat exchange the element at position p1 with the one at position p2.
  349.      * @param mat array on which the exchange is performed
  350.      * @param p1 first position of exchange (0 is the first element)
  351.      * @param p2 second position of exchange (0 is the first element)
  352.      */
  353.     private void exchangeDoubleP1WithDoubleP2(final double[] mat, final int p1, final int p2) {
  354.         final double[] a = mat.clone();
  355.         mat[p1] = a[p2];
  356.         mat[p2] = a[p1];
  357.     }

  358.     /** Return the symbol of parameter a.
  359.      * @param a the double for which we want the want the symbol
  360.      * @return -1.0 if a is lower than or equal to 0 or 1.0 if a is greater than 0
  361.      */
  362.     protected double sign(final double a) {
  363.         return (a <= 0.0) ? -1.0 : ((a > 0.0) ? 1.0 : a);
  364.     }

  365. }