File indexing completed on 2026-11-09 09:29:01
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include <boost/test/unit_test.hpp>
0010
0011 #include "Acts/Definitions/Algebra.hpp"
0012 #include "Acts/Definitions/TrackParametrization.hpp"
0013 #include "Acts/EventData/TrackContainer.hpp"
0014 #include "Acts/EventData/TrackStatePropMask.hpp"
0015 #include "Acts/EventData/VectorMultiTrajectory.hpp"
0016 #include "Acts/EventData/VectorTrackContainer.hpp"
0017 #include "Acts/Geometry/GeometryContext.hpp"
0018 #include "Acts/Surfaces/PerigeeSurface.hpp"
0019 #include "Acts/TrackFitting/GainMatrixSmoother.hpp"
0020 #include "Acts/TrackFitting/MbfSmoother.hpp"
0021 #include "Acts/TrackFitting/TrackletJoiner.hpp"
0022 #include "Acts/TrackFitting/detail/TrackletJoining.hpp"
0023 #include "ActsTests/CommonHelpers/FloatComparisons.hpp"
0024
0025 #include <algorithm>
0026 #include <array>
0027 #include <cstddef>
0028 #include <limits>
0029 #include <map>
0030 #include <vector>
0031
0032 namespace {
0033
0034 using namespace Acts;
0035
0036 const GeometryContext gctx = GeometryContext::dangerouslyDefaultConstruct();
0037
0038 constexpr std::size_t kDim = eBoundSize;
0039 const std::array<BoundIndices, 2> kProjector{eBoundLoc0, eBoundLoc1};
0040
0041
0042
0043 struct Node {
0044 int parent = -1;
0045 BoundMatrix F = BoundMatrix::Identity();
0046 BoundMatrix Q = BoundMatrix::Zero();
0047 bool hasMeasurement = false;
0048 Vector2 y = Vector2::Zero();
0049 SquareMatrix2 V = SquareMatrix2::Identity();
0050 };
0051
0052 BoundMatrix makeJacobian(double scale) {
0053 BoundMatrix f = BoundMatrix::Identity();
0054 f(eBoundLoc0, eBoundPhi) = 0.3 * scale;
0055 f(eBoundLoc1, eBoundTheta) = 0.2 * scale;
0056 f(eBoundPhi, eBoundQOverP) = 0.1 * scale;
0057 f(eBoundLoc0, eBoundQOverP) = 0.05 * scale;
0058 f(eBoundTime, eBoundLoc1) = 0.1 * scale;
0059 return f;
0060 }
0061
0062 BoundMatrix makeNoise(double scale) {
0063 BoundVector d;
0064 d << 1e-3, 1e-3, 1e-4, 1e-4, 1e-5, 1e-3;
0065 return (d * scale).asDiagonal();
0066 }
0067
0068 const BoundVector kPriorMean = [] {
0069 BoundVector m;
0070 m << 0.1, -0.2, 1.3, 0.7, 0.05, 0.0;
0071 return m;
0072 }();
0073
0074 const BoundMatrix kPriorCov = [] {
0075 BoundVector d;
0076 d << 0.04, 0.09, 0.01, 0.01, 1e-4, 0.02;
0077 return BoundMatrix(d.asDiagonal());
0078 }();
0079
0080 Matrix<2, kDim> projector() {
0081 Matrix<2, kDim> h = Matrix<2, kDim>::Zero();
0082 h(0, eBoundLoc0) = 1;
0083 h(1, eBoundLoc1) = 1;
0084 return h;
0085 }
0086
0087
0088 enum class Variant {
0089
0090
0091
0092 Base,
0093
0094
0095 TrailingHole,
0096
0097
0098 StemHole,
0099
0100 SingleMeasurement,
0101 };
0102
0103
0104 std::vector<Node> makeModel(Variant variant) {
0105 std::vector<Node> nodes(7);
0106 const SquareMatrix2 v = Vector2(4e-3, 9e-3).asDiagonal();
0107
0108 nodes[0] = {-1, BoundMatrix::Identity(), BoundMatrix::Zero(),
0109 true, Vector2(0.12, -0.23), v};
0110 nodes[1] = {0, makeJacobian(1.0), makeNoise(1.0), false, Vector2::Zero(), v};
0111 nodes[2] = {1, makeJacobian(0.8), makeNoise(1.5),
0112 true, Vector2(0.55, -0.1), v};
0113 nodes[3] = {2, makeJacobian(1.2), makeNoise(0.7),
0114 true, Vector2(0.9, 0.05), v};
0115 nodes[4] = {0, makeJacobian(-1.1), makeNoise(1.2),
0116 true, Vector2(-0.25, -0.35), v};
0117 nodes[5] = {4, makeJacobian(-0.9), makeNoise(0.9), false, Vector2::Zero(), v};
0118 nodes[6] = {5, makeJacobian(-1.3), makeNoise(1.1),
0119 true, Vector2(-0.7, -0.5), v};
0120
0121 if (variant == Variant::TrailingHole) {
0122 nodes.push_back(
0123 {3, makeJacobian(0.9), makeNoise(1.3), false, Vector2::Zero(), v});
0124 } else if (variant == Variant::StemHole) {
0125
0126 nodes.push_back({-1, BoundMatrix::Identity(), BoundMatrix::Zero(), false,
0127 Vector2::Zero(), v});
0128 nodes[0].parent = 7;
0129 nodes[0].F = makeJacobian(0.7);
0130 nodes[0].Q = makeNoise(0.8);
0131 } else if (variant == Variant::SingleMeasurement) {
0132 nodes[2].hasMeasurement = false;
0133 nodes[3].hasMeasurement = false;
0134 }
0135 return nodes;
0136 }
0137
0138
0139 std::pair<DynamicVector, DynamicMatrix> densePosterior(
0140 const std::vector<Node>& nodes) {
0141 const std::size_t n = nodes.size();
0142 const std::size_t dim = n * kDim;
0143
0144 DynamicMatrix a = DynamicMatrix::Zero(dim, dim);
0145 DynamicMatrix w = DynamicMatrix::Zero(dim, dim);
0146 DynamicVector c = DynamicVector::Zero(dim);
0147 for (std::size_t i = 0; i < n; ++i) {
0148 const Node& node = nodes[i];
0149 if (node.parent < 0) {
0150 w.block<kDim, kDim>(i * kDim, i * kDim) = kPriorCov;
0151 c.segment<kDim>(i * kDim) = kPriorMean;
0152 } else {
0153 a.block<kDim, kDim>(i * kDim, node.parent * kDim) = node.F;
0154 w.block<kDim, kDim>(i * kDim, i * kDim) = node.Q;
0155 }
0156 }
0157
0158 const DynamicMatrix g = (DynamicMatrix::Identity(dim, dim) - a).inverse();
0159 const DynamicVector priorMean = g * c;
0160 const DynamicMatrix priorCov = g * w * g.transpose();
0161
0162 DynamicMatrix information = priorCov.inverse();
0163 DynamicVector informationVector = information * priorMean;
0164 const Matrix<2, kDim> h = projector();
0165 for (std::size_t i = 0; i < n; ++i) {
0166 const Node& node = nodes[i];
0167 if (!node.hasMeasurement) {
0168 continue;
0169 }
0170 const SquareMatrix2 vInv = node.V.inverse();
0171 information.block<kDim, kDim>(i * kDim, i * kDim) +=
0172 h.transpose() * vInv * h;
0173 informationVector.segment<kDim>(i * kDim) += h.transpose() * vInv * node.y;
0174 }
0175
0176 const DynamicMatrix cov = information.inverse();
0177 const DynamicVector mean = cov * informationVector;
0178 return {mean, cov};
0179 }
0180
0181 using Container = TrackContainer<VectorTrackContainer, VectorMultiTrajectory,
0182 detail::ValueHolder>;
0183
0184
0185 std::size_t appendState(Container::TrackProxy& track, const Node& node,
0186 const BoundVector& prevMean, const BoundMatrix& prevCov,
0187 TrackStatePropMask mask = TrackStatePropMask::All) {
0188 auto ts = track.appendTrackState(mask);
0189 ts.jacobian() = node.F;
0190
0191 const BoundVector predicted = node.parent < 0 ? prevMean : node.F * prevMean;
0192 const BoundMatrix predictedCov =
0193 node.parent < 0 ? prevCov
0194 : node.F * prevCov * node.F.transpose() + node.Q;
0195 ts.predicted() = predicted;
0196 ts.predictedCovariance() = predictedCov;
0197
0198 if (node.hasMeasurement) {
0199 ts.typeFlags().setIsMeasurement();
0200 ts.allocateCalibrated(2);
0201 ts.calibrated<2>() = node.y;
0202 ts.calibratedCovariance<2>() = node.V;
0203 ts.setProjectorSubspaceIndices(kProjector);
0204
0205 const Matrix<2, kDim> h = projector();
0206 const SquareMatrix2 s = h * predictedCov * h.transpose() + node.V;
0207 const Matrix<kDim, 2> k = predictedCov * h.transpose() * s.inverse();
0208 ts.filtered() = predicted + k * (node.y - h * predicted);
0209 ts.filteredCovariance() = (BoundMatrix::Identity() - k * h) * predictedCov;
0210 } else {
0211 ts.typeFlags().setIsHole();
0212 ts.filtered() = predicted;
0213 ts.filteredCovariance() = predictedCov;
0214 }
0215 return ts.index();
0216 }
0217
0218 struct Tracklets {
0219 Container::TrackProxy first;
0220 Container::TrackStateProxy shared;
0221 Container::TrackProxy second;
0222
0223 std::map<std::size_t, std::size_t> nodeOfState;
0224
0225 std::vector<std::size_t> unsmoothed;
0226 };
0227
0228
0229
0230
0231 template <typename first_smoother_t = MbfSmoother>
0232 Tracklets buildTracklets(Container& tc, const std::vector<Node>& nodes,
0233 Variant variant) {
0234 std::map<std::size_t, std::size_t> nodeOfState;
0235 std::vector<std::size_t> unsmoothed;
0236
0237 auto first = tc.makeTrack();
0238 BoundVector mean = kPriorMean;
0239 BoundMatrix cov = kPriorCov;
0240 std::vector<std::size_t> firstNodes = {0, 1, 2, 3};
0241 if (variant == Variant::TrailingHole) {
0242 firstNodes.push_back(7);
0243 } else if (variant == Variant::StemHole) {
0244 firstNodes.insert(firstNodes.begin(), 7);
0245 } else if (variant == Variant::SingleMeasurement) {
0246 firstNodes = {0};
0247 }
0248 std::size_t lastMeasurement = kTrackIndexInvalid;
0249 std::size_t sharedIndex = kTrackIndexInvalid;
0250 for (std::size_t node : firstNodes) {
0251
0252
0253 const bool trailing = (variant == Variant::TrailingHole && node == 7);
0254 const std::size_t index = appendState(
0255 first, nodes[node], mean, cov,
0256 trailing ? (TrackStatePropMask::Predicted |
0257 TrackStatePropMask::Filtered | TrackStatePropMask::Jacobian)
0258 : TrackStatePropMask::All);
0259 nodeOfState[index] = node;
0260 auto ts = first.outermostTrackState();
0261 mean = ts.filtered();
0262 cov = ts.filteredCovariance();
0263 if (nodes[node].hasMeasurement) {
0264 lastMeasurement = index;
0265 if (sharedIndex == kTrackIndexInvalid) {
0266 sharedIndex = index;
0267 }
0268 }
0269 if (trailing) {
0270 unsmoothed.push_back(index);
0271 }
0272 }
0273 first.linkForward();
0274 BOOST_REQUIRE(
0275 first_smoother_t{}(gctx, tc.trackStateContainer(), lastMeasurement).ok());
0276
0277 auto shared = tc.trackStateContainer().getTrackState(sharedIndex);
0278 BOOST_REQUIRE(shared.hasSmoothed());
0279 BOOST_REQUIRE(shared.typeFlags().isMeasurement());
0280
0281 auto second = tc.makeTrack();
0282 mean = shared.smoothed();
0283 cov = shared.smoothedCovariance();
0284 for (std::size_t node = 4; node < 7; ++node) {
0285 const std::size_t index = appendState(second, nodes[node], mean, cov);
0286 nodeOfState[index] = node;
0287 auto ts = second.outermostTrackState();
0288 mean = ts.filtered();
0289 cov = ts.filteredCovariance();
0290 }
0291
0292 return {first, shared, second, nodeOfState, unsmoothed};
0293 }
0294
0295
0296 template <typename track_state_proxy_t>
0297 void checkStateAgainstDense(const track_state_proxy_t& ts, std::size_t node,
0298 const DynamicVector& mean, const DynamicMatrix& cov,
0299 double tol) {
0300 BOOST_REQUIRE(ts.hasSmoothed());
0301 const BoundVector expMean = mean.segment<kDim>(node * kDim);
0302 const BoundMatrix expCov = cov.block<kDim, kDim>(node * kDim, node * kDim);
0303 CHECK_CLOSE_ABS(ts.smoothed(), expMean, tol);
0304 CHECK_CLOSE_ABS(ts.smoothedCovariance(), expCov, tol);
0305
0306 const BoundMatrix asym =
0307 ts.smoothedCovariance() - ts.smoothedCovariance().transpose();
0308 BOOST_CHECK_LT(asym.cwiseAbs().maxCoeff(), 1e-15);
0309 }
0310
0311
0312
0313 void checkJoinedAgainstDense(const Container::TrackProxy& joined,
0314 const std::map<std::size_t, std::size_t>& nodeOf,
0315 const std::vector<std::size_t>& unsmoothed,
0316 const std::vector<Node>& nodes, double tol,
0317 std::size_t expectedStates) {
0318 const auto [mean, cov] = densePosterior(nodes);
0319 std::size_t count = 0;
0320 for (const auto& ts : joined.trackStatesReversed()) {
0321 ++count;
0322 if (std::find(unsmoothed.begin(), unsmoothed.end(), ts.index()) !=
0323 unsmoothed.end()) {
0324 BOOST_CHECK(!ts.hasSmoothed());
0325 continue;
0326 }
0327 BOOST_REQUIRE(nodeOf.contains(ts.index()));
0328 checkStateAgainstDense(ts, nodeOf.at(ts.index()), mean, cov, tol);
0329 }
0330 BOOST_CHECK_EQUAL(count, expectedStates);
0331 }
0332
0333
0334 template <typename smoother_t>
0335 Tracklets joinAndCheck(Container& tc, Variant variant) {
0336 const auto nodes = makeModel(variant);
0337 auto tracklets = buildTracklets(tc, nodes, variant);
0338 TrackletJoiner joiner;
0339 BOOST_REQUIRE(joiner
0340 .joinInPlace(gctx, tracklets.first, tracklets.shared,
0341 tracklets.second, smoother_t{})
0342 .ok());
0343
0344 const std::size_t onChain = variant == Variant::StemHole ? 7 : nodes.size();
0345 checkJoinedAgainstDense(tracklets.first, tracklets.nodeOfState,
0346 tracklets.unsmoothed, nodes, 1e-9, onChain);
0347 return tracklets;
0348 }
0349
0350 }
0351
0352 namespace ActsTests {
0353
0354 BOOST_AUTO_TEST_SUITE(TrackFittingSuite)
0355
0356 using Smoothers = std::tuple<MbfSmoother, GainMatrixSmoother>;
0357
0358 BOOST_AUTO_TEST_CASE_TEMPLATE(PriorNodeStepEqualsSmoothingWithPriorAsState,
0359 smoother_t, Smoothers) {
0360
0361
0362 const auto nodes = makeModel(Variant::Base);
0363 Container tc{VectorTrackContainer{}, VectorMultiTrajectory{}};
0364 auto full = tc.makeTrack();
0365 BoundVector mean = kPriorMean;
0366 BoundMatrix cov = kPriorCov;
0367 std::vector<std::size_t> fullIndices;
0368 for (std::size_t i = 0; i < 4; ++i) {
0369 fullIndices.push_back(appendState(full, nodes[i], mean, cov));
0370 mean = full.outermostTrackState().filtered();
0371 cov = full.outermostTrackState().filteredCovariance();
0372 }
0373
0374 auto prior = tc.trackStateContainer().getTrackState(fullIndices[1]);
0375 const BoundVector priorMean = prior.filtered();
0376 const BoundMatrix priorCov = prior.filteredCovariance();
0377 auto segment = tc.makeTrack();
0378 mean = priorMean;
0379 cov = priorCov;
0380 std::size_t segmentFirst = kTrackIndexInvalid;
0381 for (std::size_t i = 2; i < 4; ++i) {
0382 const std::size_t index = appendState(segment, nodes[i], mean, cov);
0383 if (i == 2) {
0384 segmentFirst = index;
0385 }
0386 mean = segment.outermostTrackState().filtered();
0387 cov = segment.outermostTrackState().filteredCovariance();
0388 }
0389
0390 BOOST_REQUIRE(
0391 MbfSmoother()(gctx, tc.trackStateContainer(), full.tipIndex()).ok());
0392 BOOST_REQUIRE(
0393 smoother_t{}(gctx, tc.trackStateContainer(), segment.tipIndex()).ok());
0394
0395 auto segmentFirstState = tc.trackStateContainer().getTrackState(segmentFirst);
0396 auto step =
0397 detail::smoothPriorNode(AnyConstTrackStateProxy{segmentFirstState},
0398 priorMean, priorCov, getDummyLogger());
0399 BOOST_REQUIRE(step.ok());
0400 CHECK_CLOSE_ABS(step->first, prior.smoothed(), 1e-10);
0401 CHECK_CLOSE_ABS(step->second, prior.smoothedCovariance(), 1e-10);
0402
0403
0404 std::vector<std::size_t> segmentIndices;
0405 for (const auto& ts : segment.trackStatesReversed()) {
0406 segmentIndices.push_back(ts.index());
0407 }
0408 for (std::size_t i = 0; i < 2; ++i) {
0409 auto a = tc.trackStateContainer().getTrackState(segmentIndices[i]);
0410 auto b = tc.trackStateContainer().getTrackState(fullIndices[3 - i]);
0411 CHECK_CLOSE_ABS(a.smoothed(), b.smoothed(), 1e-10);
0412 CHECK_CLOSE_ABS(a.smoothedCovariance(), b.smoothedCovariance(), 1e-10);
0413 }
0414 }
0415
0416 BOOST_AUTO_TEST_CASE_TEMPLATE(JoinInPlaceMatchesJointPosterior, smoother_t,
0417 Smoothers) {
0418 Container tc{VectorTrackContainer{}, VectorMultiTrajectory{}};
0419 auto tracklets = joinAndCheck<smoother_t>(tc, Variant::Base);
0420
0421
0422
0423
0424 const BoundMatrix secondStart = makeModel(Variant::Base)[4].F;
0425 BOOST_CHECK_EQUAL(tracklets.shared.previous(),
0426 tracklets.second.outermostTrackState().index());
0427 CHECK_CLOSE_ABS(tracklets.shared.jacobian(), secondStart.inverse(), 1e-12);
0428 BOOST_CHECK_EQUAL(tracklets.first.stemIndex(), tracklets.second.stemIndex());
0429 std::vector<std::size_t> backward;
0430 for (const auto& ts : tracklets.first.trackStatesReversed()) {
0431 backward.push_back(ts.index());
0432 }
0433 std::vector<std::size_t> forward;
0434 for (const auto& ts : tracklets.first.trackStates()) {
0435 forward.push_back(ts.index());
0436 }
0437 BOOST_REQUIRE_EQUAL(backward.size(), 7u);
0438 BOOST_REQUIRE_EQUAL(forward.size(), 7u);
0439 for (std::size_t i = 0; i < 7; ++i) {
0440 BOOST_CHECK_EQUAL(forward[i], backward[6 - i]);
0441 }
0442 }
0443
0444 BOOST_AUTO_TEST_CASE_TEMPLATE(JoinSkipsTrailingHoles, smoother_t, Smoothers) {
0445
0446
0447 Container tc{VectorTrackContainer{}, VectorMultiTrajectory{}};
0448 auto tracklets = joinAndCheck<smoother_t>(tc, Variant::TrailingHole);
0449 BOOST_REQUIRE_EQUAL(tracklets.unsmoothed.size(), 1u);
0450 BOOST_CHECK_EQUAL(tracklets.unsmoothed.front(), tracklets.first.tipIndex());
0451 }
0452
0453 BOOST_AUTO_TEST_CASE_TEMPLATE(JoinSmoothsStemStatesBeforeShared, smoother_t,
0454 Smoothers) {
0455
0456
0457
0458 Container tc{VectorTrackContainer{}, VectorMultiTrajectory{}};
0459 auto tracklets = joinAndCheck<smoother_t>(tc, Variant::StemHole);
0460 const auto nodes = makeModel(Variant::StemHole);
0461 const auto [mean, cov] = densePosterior(nodes);
0462 std::size_t stemIndex = kTrackIndexInvalid;
0463 for (const auto& [index, node] : tracklets.nodeOfState) {
0464 if (node == 7) {
0465 stemIndex = index;
0466 }
0467 }
0468 BOOST_REQUIRE_NE(stemIndex, kTrackIndexInvalid);
0469 checkStateAgainstDense(tc.trackStateContainer().getTrackState(stemIndex), 7,
0470 mean, cov, 1e-9);
0471
0472 BOOST_CHECK_NE(tracklets.shared.previous(), stemIndex);
0473 }
0474
0475 BOOST_AUTO_TEST_CASE(JoinCopyLeavesInputsUntouched) {
0476 const auto nodes = makeModel(Variant::Base);
0477 Container tc{VectorTrackContainer{}, VectorMultiTrajectory{}};
0478 auto tracklets = buildTracklets(tc, nodes, Variant::Base);
0479 const BoundVector sharedBefore = tracklets.shared.smoothed();
0480 const std::size_t firstStates = tracklets.first.nTrackStates();
0481 const std::size_t secondStates = tracklets.second.nTrackStates();
0482 const std::size_t tracksBefore = tc.size();
0483
0484 TrackletJoiner joiner;
0485 auto joined = joiner.joinCopy(gctx, tc, tracklets.first, tracklets.shared,
0486 tracklets.second);
0487 BOOST_REQUIRE(joined.ok());
0488
0489
0490
0491 std::vector<std::size_t> order;
0492 for (const auto& ts : tracklets.first.trackStatesReversed()) {
0493 order.push_back(tracklets.nodeOfState.at(ts.index()));
0494 }
0495 std::vector<std::size_t> secondOrder;
0496 for (const auto& ts : tracklets.second.trackStatesReversed()) {
0497 secondOrder.push_back(tracklets.nodeOfState.at(ts.index()));
0498 }
0499 order.insert(order.end(), secondOrder.rbegin(), secondOrder.rend());
0500 std::map<std::size_t, std::size_t> nodeOfCopy;
0501 std::size_t pos = 0;
0502 for (const auto& ts : joined->trackStatesReversed()) {
0503 nodeOfCopy[ts.index()] = order.at(pos++);
0504 }
0505 checkJoinedAgainstDense(*joined, nodeOfCopy, {}, nodes, 1e-9, nodes.size());
0506
0507 CHECK_CLOSE_ABS(tracklets.shared.smoothed(), sharedBefore, 0.);
0508 BOOST_CHECK_EQUAL(tracklets.first.nTrackStates(), firstStates);
0509 BOOST_CHECK_EQUAL(tracklets.second.nTrackStates(), secondStates);
0510
0511 BOOST_CHECK_EQUAL(tc.size(), tracksBefore + 1);
0512 BOOST_CHECK_EQUAL(joined->nTrackStates(), firstStates + secondStates);
0513 }
0514
0515 BOOST_AUTO_TEST_CASE(ConditioningRejectsStateOffTheChain) {
0516 const auto nodes = makeModel(Variant::Base);
0517 Container tc{VectorTrackContainer{}, VectorMultiTrajectory{}};
0518 auto tracklets = buildTracklets(tc, nodes, Variant::Base);
0519 auto res = detail::conditionSmoothedStatesOnSharedState(
0520 tc.trackStateContainer(), tracklets.second.tipIndex(),
0521 tracklets.first.tipIndex(), tracklets.shared.smoothed(),
0522 tracklets.shared.smoothedCovariance(), getDummyLogger());
0523 BOOST_CHECK(!res.ok());
0524 }
0525
0526 BOOST_AUTO_TEST_CASE_TEMPLATE(ConditioningPreservesFilterHistory,
0527 first_smoother_t, Smoothers) {
0528 for (const auto variant : {Variant::Base, Variant::SingleMeasurement}) {
0529 const auto nodes = makeModel(variant);
0530 Container tc{VectorTrackContainer{}, VectorMultiTrajectory{}};
0531 auto t = buildTracklets<first_smoother_t>(tc, nodes, variant);
0532 std::map<std::size_t, std::pair<BoundVector, BoundMatrix>> predicted;
0533 std::map<std::size_t, std::pair<BoundVector, BoundMatrix>> filtered;
0534 for (const auto& ts : t.first.trackStatesReversed()) {
0535 predicted[ts.index()] = {ts.predicted(), ts.predictedCovariance()};
0536 filtered[ts.index()] = {ts.filtered(), ts.filteredCovariance()};
0537 }
0538 BOOST_REQUIRE(
0539 TrackletJoiner{}.joinInPlace(gctx, t.first, t.shared, t.second).ok());
0540 checkJoinedAgainstDense(t.first, t.nodeOfState, {}, nodes, 1e-9,
0541 variant == Variant::Base ? 7 : 4);
0542 for (const auto& [index, estimate] : filtered) {
0543 auto ts = tc.trackStateContainer().getTrackState(index);
0544 CHECK_CLOSE_ABS(ts.filtered(), estimate.first, 0.);
0545 CHECK_CLOSE_ABS(ts.filteredCovariance(), estimate.second, 0.);
0546 CHECK_CLOSE_ABS(ts.predicted(), predicted.at(index).first, 0.);
0547 CHECK_CLOSE_ABS(ts.predictedCovariance(), predicted.at(index).second, 0.);
0548 }
0549 }
0550 }
0551
0552 BOOST_AUTO_TEST_CASE(JoinBuildsMissingForwardLinks) {
0553 Container tc{VectorTrackContainer{}, VectorMultiTrajectory{}};
0554 auto t = buildTracklets(tc, makeModel(Variant::Base), Variant::Base);
0555
0556 for (auto ts : t.first.trackStatesReversed()) {
0557 ts.component<TrackIndexType>(detail_tp::kNextKey) = kTrackIndexInvalid;
0558 }
0559 t.first.stemIndex() = kTrackIndexInvalid;
0560 BOOST_REQUIRE(
0561 TrackletJoiner{}.joinInPlace(gctx, t.first, t.shared, t.second).ok());
0562 std::vector<std::size_t> backward;
0563 for (const auto& ts : t.first.trackStatesReversed()) {
0564 backward.push_back(ts.index());
0565 }
0566 std::vector<std::size_t> forward;
0567 for (const auto& ts : t.first.trackStates()) {
0568 forward.push_back(ts.index());
0569 }
0570 std::ranges::reverse(backward);
0571 BOOST_CHECK_EQUAL_COLLECTIONS(forward.begin(), forward.end(),
0572 backward.begin(), backward.end());
0573 }
0574
0575 BOOST_AUTO_TEST_CASE(JoinEmptySecondPassKeepsStatesAndTargetEstimate) {
0576 Container tc{VectorTrackContainer{}, VectorMultiTrajectory{}};
0577 auto t = buildTracklets(tc, makeModel(Variant::Base), Variant::Base);
0578 auto second = tc.makeTrack();
0579 second.copyFromWithoutStates(t.first);
0580 const auto target = Surface::makeShared<PerigeeSurface>(Vector3::Zero());
0581 second.setReferenceSurface(target);
0582 second.parameters() = kPriorMean;
0583 second.covariance() = kPriorCov;
0584 const auto tip = t.first.tipIndex();
0585 const auto stem = t.first.stemIndex();
0586 std::vector<std::pair<BoundVector, BoundMatrix>> before;
0587 for (const auto& ts : t.first.trackStatesReversed()) {
0588 before.emplace_back(ts.smoothed(), ts.smoothedCovariance());
0589 }
0590 TrackletJoiner joiner;
0591 auto copy = joiner.joinCopy(gctx, tc, t.first, t.shared, second);
0592 BOOST_REQUIRE(copy.ok());
0593 BOOST_REQUIRE(joiner.joinInPlace(gctx, t.first, t.shared, second).ok());
0594 BOOST_CHECK_EQUAL(t.first.tipIndex(), tip);
0595 BOOST_CHECK_EQUAL(t.first.stemIndex(), stem);
0596 for (const auto& joined : {t.first, *copy}) {
0597 BOOST_CHECK_EQUAL(&joined.referenceSurface(), target.get());
0598 CHECK_CLOSE_ABS(joined.parameters(), kPriorMean, 0.);
0599 CHECK_CLOSE_ABS(joined.covariance(), kPriorCov, 0.);
0600 BOOST_REQUIRE_EQUAL(joined.nTrackStates(), before.size());
0601 std::size_t i = 0;
0602 for (const auto& ts : joined.trackStatesReversed()) {
0603 CHECK_CLOSE_ABS(ts.smoothed(), before[i].first, 0.);
0604 CHECK_CLOSE_ABS(ts.smoothedCovariance(), before[i].second, 0.);
0605 ++i;
0606 }
0607 }
0608 }
0609
0610 BOOST_AUTO_TEST_CASE(RejectInvalidCovariances) {
0611 Container tc{VectorTrackContainer{}, VectorMultiTrajectory{}};
0612 auto t = buildTracklets(tc, makeModel(Variant::Base), Variant::Base);
0613 for (double variance : {0., -1., std::numeric_limits<double>::quiet_NaN(),
0614 std::numeric_limits<double>::infinity()}) {
0615 BoundMatrix cov = BoundMatrix::Identity();
0616 cov(eBoundLoc0, eBoundLoc0) = variance;
0617 t.shared.smoothedCovariance() = cov;
0618 t.shared.predictedCovariance() = cov;
0619 BOOST_CHECK(!detail::makeSharedStateCondition(
0620 AnyConstTrackStateProxy{t.shared}, kPriorMean, kPriorCov,
0621 getDummyLogger())
0622 .ok());
0623 BOOST_CHECK(!detail::smoothPriorNode(AnyConstTrackStateProxy{t.shared},
0624 kPriorMean, kPriorCov,
0625 getDummyLogger())
0626 .ok());
0627 }
0628 }
0629
0630 BOOST_AUTO_TEST_SUITE_END()
0631
0632 }