0.5.0
Loading...
Searching...
No Matches
Search.hpp
Go to the documentation of this file.
1// This file is part of INSTINCT, the INS Toolkit for Integrated
2// Navigation Concepts and Training by the Institute of Navigation of
3// the University of Stuttgart, Germany.
4//
5// This Source Code Form is subject to the terms of the Mozilla Public
6// License, v. 2.0. If a copy of the MPL was not distributed with this
7// file, You can obtain one at https://mozilla.org/MPL/2.0/.
8
9/// @file Search.hpp
10/// @brief Ambiguity Search algorithms
11/// @author T. Topp (topp@ins.uni-stuttgart.de)
12/// @date 2023-09-13
13/// @note Algorithm mostly taken from \cite deJonge1996 de Jonge 1996.
14/// Matlab code from https://www.tudelft.nl/citg/over-faculteit/afdelingen/geoscience-remote-sensing/research/lambda/lambda was used to clarify points in literature.
15
16#pragma once
17
18#include <Eigen/Core>
19#include <cmath>
21#include <Eigen/src/Core/ArithmeticSequence.h>
22#include <gcem.hpp>
23#include "EllipsoidalRegion.hpp"
24#include <limits>
25
26namespace NAV::Ambiguity
27{
28
29/// @brief Searches for the integer ambiguities by integer rounding
30/// @param[in] a Float ambiguity vector [cycles]
31/// @return Integer ambiguity vector
32/// @note See \cite SpringerHandbookGNSS2017 Springer Handbook GNSS, ch. 23.2.2
33template<typename Derived>
34typename Derived::PlainObject integerSearchRounding(const Eigen::MatrixBase<Derived>& a)
35{
36 static_assert(Derived::ColsAtCompileTime == Eigen::Dynamic || Derived::ColsAtCompileTime == 1);
37 INS_ASSERT_USER_ERROR(a.cols() == 1, "Parameter 'a' has to be a vector");
38
39 return a.unaryExpr([](const double& x) { return std::round(x); });
40}
41
42/// @brief Searches for the integer ambiguities by integer bootstrapping
43/// @param[in] a Decorrelated float ambiguity vector [cycles]
44/// @param[in] Qa Variance/covariance matrix of the ambiguities
45/// @return Integer ambiguity vector
46/// @note See \cite SpringerHandbookGNSS2017 Springer Handbook GNSS, ch. 23.2.3
47template<typename DerivedA, typename DerivedQ>
48typename DerivedA::PlainObject integerSearchBootstrapping(const Eigen::MatrixBase<DerivedA>& a, const Eigen::MatrixBase<DerivedQ>& Qa)
49{
50 static_assert(DerivedA::ColsAtCompileTime == Eigen::Dynamic || DerivedA::ColsAtCompileTime == 1);
51 INS_ASSERT_USER_ERROR(a.cols() == 1, "Parameter 'a' has to be a vector");
52
53 typename DerivedA::PlainObject a_fix = a;
54 typename DerivedA::PlainObject a_float = a;
55
56 for (Eigen::Index n = 0; n < a.rows(); n++)
57 {
58 for (Eigen::Index j = 0; j < n; j++)
59 {
60 a_float(n) -= Qa(n, j) / Qa(j, j) * (a_float(j) - a_fix(j));
61 }
62
63 a_fix(n) = std::round(a_float(n));
64 }
65
66 return a_fix;
67}
68
69/// @brief Performs an integer least-squares to search for integer candidates for the ambiguities
70/// @param[in] a Decorrelated float ambiguity vector [cycles]
71/// @param[in] Q Variance/covariance matrix of the ambiguities
72/// @param[in] L_LTDL_Q Lower-triangular matrix from the L^T * D * L decomposition of Q
73/// @param[in] D_LTDL_Q Diagonal entries from the L^T * D * L decomposition of Q
74/// @param[in] numCandidates Requested number of candidates (default = 2)
75/// @return Pair of: First a list of integer candidates (last column is the most likely), Vector with squared norm of the integer candidates
76/// @note See \cite deJonge1996 de Jonge 1996, Algorithm FI71
77template<typename DerivedA, typename DerivedQ, typename DerivedL, typename DerivedD>
78std::pair<Eigen::MatrixXd, Eigen::VectorXd>
79 integerLeastSquaresSearch(const Eigen::MatrixBase<DerivedA>& a, const Eigen::MatrixBase<DerivedQ>& Q,
80 const Eigen::MatrixBase<DerivedL>& L_LTDL_Q, const Eigen::MatrixBase<DerivedD>& D_LTDL_Q,
81 const Eigen::Index& numCandidates = 2)
82{
83 static_assert(DerivedA::ColsAtCompileTime == Eigen::Dynamic || DerivedA::ColsAtCompileTime == 1);
84 INS_ASSERT_USER_ERROR(a.cols() == 1, "Parameter 'a' has to be a vector");
85 static_assert(DerivedD::ColsAtCompileTime == Eigen::Dynamic || DerivedD::ColsAtCompileTime == 1);
86 INS_ASSERT_USER_ERROR(D_LTDL_Q.cols() == 1, "Parameter 'D_LTDL_Q' has to be a vector");
87 INS_ASSERT_USER_ERROR(a.rows() == D_LTDL_Q.rows(), "Parameter 'a' and 'D_LTDL_Q' must have same dimension");
88
89 using Eigen::seq;
90
91 auto n = a.rows(); // Amount of ambiguities
92 if (n == 1) { return {}; }
93 double chi2 = calcChi2(a, Q, L_LTDL_Q, D_LTDL_Q, numCandidates, 1.0); // Size of the ellipsoidal region
94 LOG_DATA("chi2 = {}", chi2);
95
96 // Q^-1 = L * D * L^T
97 typename DerivedL::PlainObject L = L_LTDL_Q.inverse();
98 typename DerivedD::PlainObject D = (1.0 / D_LTDL_Q.array()).matrix();
99 LOG_DATA("L =\n{}", L);
100 LOG_DATA("D = {}", D.transpose());
101
102 Eigen::VectorXd right = Eigen::VectorXd::Zero(n + 1);
103 right(n) = chi2;
104 Eigen::VectorXd left = Eigen::VectorXd::Zero(n + 1);
105
106 Eigen::VectorXd lef = Eigen::VectorXd::Zero(n);
107 Eigen::VectorXd end = Eigen::VectorXd::Zero(n);
108 Eigen::VectorXd dist = Eigen::VectorXd::Zero(n);
109 Eigen::VectorXd disall = Eigen::VectorXd::Zero(numCandidates);
110 Eigen::MatrixXd cands = Eigen::MatrixXd::Zero(n, numCandidates);
111 double tmax = 0.0;
112 Eigen::Index imax = 0;
113
114 bool ende = false;
115 Eigen::Index ncan = 0;
116
117 Eigen::VectorXd dq = Eigen::VectorXd::Zero(n);
118 dq.head(n - 1) = D(seq(1, n - 1)).array() / D(seq(0, n - 2)).array();
119 dq(n - 1) = 1.0 / D(n - 1);
120 LOG_DATA("dq = {}", dq.transpose());
121
122 Eigen::Index i = n;
123 Eigen::Index iold = i;
124
125 auto BACKTS = [](const Eigen::Index& n, Eigen::Index& i, const auto& end, auto& dist, const auto& lef, auto& left, auto& ende) {
126 for (i++; i < n; i++)
127 {
128 if (dist(i) <= end(i))
129 {
130 dist(i)++;
131 left(i) = std::pow(dist(i) + lef(i), 2);
132 break;
133 }
134 if (i == n - 1) { ende = true; }
135 }
136 };
137
138 auto COLLECTs = [&chi2, &a]<typename DerD>(const Eigen::Index& n, const Eigen::Index& maxcan, const Eigen::MatrixBase<DerD>& D, const auto& lef, const auto& left, const auto& right,
139 auto& dist, auto& end, Eigen::Index& ncan, auto& disall, auto& cands, double& tmax, Eigen::Index& imax) {
140 auto STOREs = [&n, &a](const Eigen::Index& ican, const Eigen::Index& ipos, Eigen::Index& imax, const double& t, double& tmax, const auto& dist, auto& cands, auto& disall) {
141 cands(seq(0, n - 1), ipos) = dist(seq(0, n - 1)) + a;
142 LOG_DATA(" cands =\n{}", cands);
143 disall(ipos) = t;
144 LOG_DATA(" disall = {}", disall.transpose());
145 tmax = t;
146 imax = ipos;
147 for (Eigen::Index i = 0; i < ican; i++)
148 {
149 if (disall(i) > tmax)
150 {
151 imax = i;
152 tmax = disall(i);
153 }
154 }
155 };
156
157 double t = chi2 - (right(0) - left(0)) * D(0);
158 LOG_DATA(" t = {}", t);
159 end(0)++;
160 LOG_DATA(" maxcan = {}", maxcan);
161 LOG_DATA(" tmax = {}", tmax);
162 LOG_DATA(" end(0) = {}", end(0));
163 LOG_DATA(" dist(0) = {}", dist(0));
164 while (dist(0) <= end(0))
165 {
166 LOG_DATA(" ncan = {}", ncan);
167 if (ncan < maxcan)
168 {
169 ncan++;
170 STOREs(ncan, ncan - 1, imax, t, tmax, dist, cands, disall);
171 }
172 else if (t < tmax)
173 {
174 STOREs(maxcan, imax, imax, t, tmax, dist, cands, disall);
175 }
176 t += (2 * (dist(0) + lef(0)) + 1) * D(0);
177 LOG_DATA(" t = {}", t);
178 dist(0)++;
179 LOG_DATA(" dist(0) = {}", dist(0));
180 }
181 };
182
183 while (!ende)
184 {
185 i--;
186 LOG_DATA("i = {}", i);
187
188 if (iold <= i)
189 {
190 lef(i) += L(i + 1, i);
191 }
192 else
193 {
194 lef(i) = L(seq(i + 1, n - 1), i).dot(dist(seq(i + 1, n - 1)));
195 }
196 LOG_DATA(" lef = {}", lef.transpose());
197 iold = i;
198 right(i) = (right(i + 1) - left(i + 1)) * dq(i);
199 LOG_DATA(" right = {}", right.transpose());
200 auto reach = std::sqrt(right(i));
201 LOG_DATA(" reach = {}", reach);
202 auto delta = a(i) - reach - lef(i);
203 LOG_DATA(" delta = {}", delta);
204 dist(i) = std::ceil(delta) - a(i);
205 LOG_DATA(" dist = {}", dist.transpose());
206 if (dist(i) > reach - lef(i)) // nothing at this level -> backtrack
207 {
208 BACKTS(n, i, end, dist, lef, left, ende);
209 continue;
210 }
211 // set the right border
212 end(i) = reach - lef(i) - 1;
213 LOG_DATA(" end = {}", end.transpose());
214 left(i) = std::pow(dist(i) + lef(i), 2);
215 LOG_DATA(" left = {}", left.transpose());
216
217 if (i == 0)
218 {
219 COLLECTs(n, numCandidates, D, lef, left, right, dist, end, ncan, disall, cands, tmax, imax);
220 BACKTS(n, i, end, dist, lef, left, ende);
221 }
222 }
223
224 // Sort candidates by norm
225 auto p = sort_permutation(disall, std::less<>{});
228
229 return { cands.rightCols(ncan), disall.tail(ncan) };
230}
231
232/// @brief Performs an integer least-squares to search for integer candidates for the ambiguities using the search-and-shrink technique (MLAMBDA)
233/// @param[in] zh Decorrelated float ambiguity vector [cycles]
234/// @param[in] L_LTDL_Q Lower-triangular matrix from the L^T * D * L decomposition of Q
235/// @param[in] D_LTDL_Q Diagonal entries from the L^T * D * L decomposition of Q
236/// @param[in] numCandidates Requested number of candidates (default = 2)
237/// @return Pair of: First a list of integer candidates (last column is the most likely), Vector with squared norm of the integer candidates
238/// @note See \cite chang2005 Chang 2005, Argorithm 3.3
239/// @note See \cite deJonge1996 de Jonge 1996, ch. 4.8
240/// @note See ssearch.m file from TUDelft Matlab code
241template<typename DerivedA, typename DerivedL, typename DerivedD>
242std::pair<Eigen::MatrixXd, Eigen::VectorXd>
243 integerLeastSquaresSearchAndShrink(const Eigen::MatrixBase<DerivedA>& zh, const Eigen::MatrixBase<DerivedL>& L_LTDL_Q,
244 const Eigen::MatrixBase<DerivedD>& D_LTDL_Q, const Eigen::Index& numCandidates = 2)
245{
246 static_assert(DerivedA::ColsAtCompileTime == Eigen::Dynamic || DerivedA::ColsAtCompileTime == 1);
247 INS_ASSERT_USER_ERROR(zh.cols() == 1, "Parameter 'zh' has to be a vector");
248 static_assert(DerivedD::ColsAtCompileTime == Eigen::Dynamic || DerivedD::ColsAtCompileTime == 1);
249 INS_ASSERT_USER_ERROR(D_LTDL_Q.cols() == 1, "Parameter 'D' has to be a vector");
250 INS_ASSERT_USER_ERROR(zh.rows() == D_LTDL_Q.rows(), "Parameter 'zh' and 'D' must have same dimension");
251
252 using Eigen::seq;
253
254 auto n = zh.rows(); // Amount of ambiguities
255 if (n == 1) { return {}; }
256
257 auto sgn = [](auto x) {
258 int sgn = gcem::sgn(x);
259 return sgn ? sgn : 1;
260 };
261
262 Eigen::VectorXd dist = Eigen::VectorXd::Zero(n);
263 Eigen::VectorXd zb = Eigen::VectorXd::Zero(n);
264 Eigen::VectorXd z = Eigen::VectorXd::Zero(n);
265 Eigen::VectorXd step = Eigen::VectorXd::Zero(n);
266 Eigen::MatrixXd S = Eigen::MatrixXd::Zero(n, n);
267
268 Eigen::MatrixXd cands = Eigen::MatrixXd::Zero(n, numCandidates);
269 Eigen::VectorXd sqnorm = Eigen::VectorXd::Zero(numCandidates);
270
271 auto maxDist = std::numeric_limits<double>::infinity(); // current chi^2
272 auto k = n - 1;
273 Eigen::Index count = 0; // the number of candidates
274
275 zb(n - 1) = zh(n - 1);
276 z(n - 1) = std::round(zb(n - 1));
277 double y = zb(n - 1) - z(n - 1);
278 step(n - 1) = sgn(y);
279 auto imax = numCandidates - 1;
280 for (size_t loop = 0; loop < 10000; loop++)
281 {
282 double newDist = dist(k) + std::pow(y, 2) / D_LTDL_Q(k);
283 if (newDist < maxDist)
284 {
285 if (k != 0) // Case 1: move down
286 {
287 k--;
288 dist(k) = newDist;
289 S(k, seq(0, k)) = S(k + 1, seq(0, k)) + (z(k + 1) - zb(k + 1)) * L_LTDL_Q(k + 1, seq(0, k));
290 zb(k) = zh(k) + S(k, k);
291 z(k) = std::round(zb(k));
292 y = zb(k) - z(k);
293 step(k) = sgn(y);
294 }
295 else // Case 2: store the found candidate and try next valid integer
296 {
297 if (count < numCandidates) // store the first p − 1 initial points
298 {
299 if (count == 0 || newDist > sqnorm(imax)) { imax = count; } // Addition from Rtklib
300 cands.col(count) = z;
301 sqnorm(count) = newDist;
302 count++;
303 }
304 else
305 {
306 if (newDist < sqnorm(imax))
307 {
308 cands.col(imax) = z;
309 sqnorm(imax) = newDist;
310 sqnorm.maxCoeff(&imax);
311 }
312 maxDist = sqnorm(imax);
313 }
314 z(0) += step(0); // next valid integer
315 y = zb(0) - z(0);
316 step(0) = -step(0) - sgn(step(0));
317 }
318 }
319 else // Case 3: exit or move up
320 {
321 if (k == n - 1) { break; }
322
323 k++; // move up
324 z(k) += step(k); // next valid integer
325 y = zb(k) - z(k);
326 step(k) = -step(k) - sgn(step(k));
327 }
328 }
329
330 // Sort candidates by norm
331 auto p = sort_permutation(sqnorm, std::less<>{});
334
335 return { cands.rightCols(count), sqnorm.tail(count) };
336}
337
338} // namespace NAV::Ambiguity
#define INS_ASSERT_USER_ERROR(_EXP, _MSG)
Assert function with message.
Definition Assert.h:21
Calculate the ellipsoidal region Chi^2.
#define LOG_DATA
All output which occurs repeatedly every time observations are received.
Definition Logger.hpp:29
Sorting algorithms.
std::pair< Eigen::MatrixXd, Eigen::VectorXd > integerLeastSquaresSearchAndShrink(const Eigen::MatrixBase< DerivedA > &zh, const Eigen::MatrixBase< DerivedL > &L_LTDL_Q, const Eigen::MatrixBase< DerivedD > &D_LTDL_Q, const Eigen::Index &numCandidates=2)
Performs an integer least-squares to search for integer candidates for the ambiguities using the sear...
Definition Search.hpp:243
Derived::PlainObject integerSearchRounding(const Eigen::MatrixBase< Derived > &a)
Searches for the integer ambiguities by integer rounding.
Definition Search.hpp:34
std::pair< Eigen::MatrixXd, Eigen::VectorXd > integerLeastSquaresSearch(const Eigen::MatrixBase< DerivedA > &a, const Eigen::MatrixBase< DerivedQ > &Q, const Eigen::MatrixBase< DerivedL > &L_LTDL_Q, const Eigen::MatrixBase< DerivedD > &D_LTDL_Q, const Eigen::Index &numCandidates=2)
Performs an integer least-squares to search for integer candidates for the ambiguities.
Definition Search.hpp:79
DerivedA::PlainObject integerSearchBootstrapping(const Eigen::MatrixBase< DerivedA > &a, const Eigen::MatrixBase< DerivedQ > &Qa)
Searches for the integer ambiguities by integer bootstrapping.
Definition Search.hpp:48
double calcChi2(const Eigen::MatrixBase< DerivedA > &a, const Eigen::MatrixBase< DerivedQ > &Q, const Eigen::MatrixBase< DerivedL > &L_LTDL_Q, const Eigen::MatrixBase< DerivedD > &D_LTDL_Q, const Eigen::Index &ncand=2, double factor=1.5)
Calculates , the size of the ellipsoidal region.
std::vector< size_t > sort_permutation(const std::vector< T > &vec, Compare compare)
Gives the permutation to sort the std::vector.
Definition Sort.hpp:30
void apply_permutation_in_place(std::vector< T > &vec, const std::vector< size_t > &p)
Sort a std::vector with a permutation.
Definition Sort.hpp:112
void apply_permutation_colwise_in_place(Eigen::Matrix< Scalar, Rows, Cols > &m, const std::vector< size_t > &p)
Sort a matrix col-wise with a permutation.
Definition Sort.hpp:193