File indexing completed on 2026-07-16 08:08:51
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include <boost/test/unit_test.hpp>
0010
0011 #include <chrono>
0012 #include <format>
0013 #include <future>
0014 #include <iostream>
0015 #include <random>
0016 #include <ranges>
0017 #include <span>
0018 #include <thread>
0019
0020 #include <TFile.h>
0021 #include <TH1D.h>
0022 #include <TTree.h>
0023
0024 #include "StrawHitGeneratorHelper.hpp"
0025
0026 using TimePoint_t = std::chrono::system_clock::time_point;
0027 using Fitter_t = CompositeSpacePointLineFitter;
0028
0029 constexpr auto logLvl = Acts::Logging::Level::INFO;
0030 constexpr std::size_t nEvents = 1;
0031 constexpr long int nThreads = 1;
0032 std::mutex writeMutex{};
0033
0034 ACTS_LOCAL_LOGGER(getDefaultLogger("StrawLineFitterTest", logLvl));
0035
0036 namespace ActsTests {
0037
0038 using GenCfg_t = MeasurementGenerator::Config;
0039
0040 #define DECLARE_BRANCH(dType, bName) \
0041 dType bName{}; \
0042 outTree->Branch(#bName, &bName);
0043
0044 long int runFitTest(Fitter_t::Config fitCfg, GenCfg_t genCfg,
0045 const std::string& testName, const unsigned seed,
0046 TFile& outFile) {
0047
0048 auto outTree = std::make_unique<TTree>(
0049 std::format("{:}Tree", testName).c_str(), "MonitorTree");
0050 outTree->SetDirectory(nullptr);
0051
0052 DECLARE_BRANCH(double, trueY0);
0053 DECLARE_BRANCH(double, trueX0);
0054 DECLARE_BRANCH(double, trueTheta);
0055 DECLARE_BRANCH(double, truePhi);
0056 DECLARE_BRANCH(double, trueT0);
0057 DECLARE_BRANCH(double, trueProjTheta);
0058 DECLARE_BRANCH(double, trueProjPhi);
0059
0060 DECLARE_BRANCH(double, recoY0);
0061 DECLARE_BRANCH(double, recoX0);
0062 DECLARE_BRANCH(double, recoTheta);
0063 DECLARE_BRANCH(double, recoPhi);
0064 DECLARE_BRANCH(double, recoT0);
0065 DECLARE_BRANCH(double, recoProjTheta);
0066 DECLARE_BRANCH(double, recoProjPhi);
0067
0068 DECLARE_BRANCH(double, sigmaY0);
0069 DECLARE_BRANCH(double, sigmaX0);
0070 DECLARE_BRANCH(double, sigmaTheta);
0071 DECLARE_BRANCH(double, sigmaPhi);
0072 DECLARE_BRANCH(double, sigmaT0);
0073
0074 DECLARE_BRANCH(double, chi2);
0075 DECLARE_BRANCH(unsigned, nIter);
0076 DECLARE_BRANCH(unsigned, nDoF);
0077 DECLARE_BRANCH(char, converged);
0078
0079 RandomEngine engine{seed};
0080 Fitter_t fitter{fitCfg, getDefaultLogger(
0081 std::format("LineFitter_{:}", testName), logLvl)};
0082
0083 TimePoint_t start = std::chrono::system_clock::now();
0084
0085 ACTS_INFO("Start test " << testName << ".");
0086
0087
0088
0089
0090
0091
0092
0093 auto fillPars = [](const auto pars, double& y0, double& x0, double& theta,
0094 double& phi) {
0095 y0 = pars[toUnderlying(FitParIndex::y0)];
0096 x0 = pars[toUnderlying(FitParIndex::x0)];
0097 Vector3 properDir =
0098 makeDirectionFromPhiTheta(pars[toUnderlying(FitParIndex::phi)],
0099 pars[toUnderlying(FitParIndex::theta)]);
0100 theta =
0101 VectorHelpers::theta(copySign(properDir, properDir.z())) / 1._degree;
0102 phi = VectorHelpers::phi(copySign(properDir, properDir.z())) / 1._degree;
0103 };
0104
0105 auto fillProjected = [](const auto pars, double& projTheta, double& projPhi) {
0106 auto dir =
0107 makeDirectionFromPhiTheta(pars[toUnderlying(FitParIndex::phi)],
0108 pars[toUnderlying(FitParIndex::theta)]);
0109 projTheta = std::atan(dir[ePos1] / dir[ePos2]) / 1._degree;
0110 projPhi = std::atan(dir[ePos0] / dir[ePos2]) / 1._degree;
0111 };
0112
0113 auto calibrator = std::make_unique<SpCalibrator>();
0114 std::size_t goodFits{0};
0115 for (std::size_t evt = 0; evt < nEvents; ++evt) {
0116 const auto line = generateLine(engine, logger());
0117 fillPars(line.parameters(), trueY0, trueX0, trueTheta, truePhi);
0118 fillProjected(line.parameters(), trueProjTheta, trueProjPhi);
0119 const double t0 = uniform{-50._ns, 50._ns}(engine);
0120 trueT0 = t0 / 1._ns;
0121
0122 using FitOpts_t = Fitter_t::FitOptions<Container_t, SpCalibrator>;
0123
0124 FitOpts_t fitOpts{};
0125 fitOpts.calibrator = calibrator.get();
0126
0127 fitOpts.selector.connect<&isGoodHit>();
0128 fitOpts.measurements =
0129 MeasurementGenerator::spawn(line, t0, engine, genCfg, logger());
0130 fitOpts.startParameters = startParameters(line, fitOpts.measurements);
0131 fillPars(fitOpts.startParameters, recoY0, recoX0, recoTheta, recoPhi);
0132
0133
0134
0135 auto result = fitter.fit(std::move(fitOpts));
0136 if (!result.converged) {
0137 ACTS_DEBUG("Fit " << outTree->GetName() << " failed.");
0138 converged = 0;
0139 chi2 = -1.;
0140 nDoF = 1;
0141 nIter = fitter.config().maxIter;
0142 outTree->Fill();
0143 continue;
0144 }
0145
0146 ACTS_DEBUG("Fit Successful.");
0147 converged = 1;
0148 ++goodFits;
0149 fillPars(result.parameters, recoY0, recoX0, recoTheta, recoPhi);
0150 fillProjected(result.parameters, recoProjTheta, recoProjPhi);
0151
0152 recoT0 = result.parameters[toUnderlying(FitParIndex::t0)] / 1._ns;
0153
0154 auto extractUncert = [&result](const auto idx) {
0155 return std::sqrt(result.covariance(toUnderlying(idx), toUnderlying(idx)));
0156 };
0157 sigmaY0 = extractUncert(FitParIndex::y0);
0158 sigmaX0 = extractUncert(FitParIndex::x0);
0159 sigmaTheta = extractUncert(FitParIndex::theta) / 1._degree;
0160 sigmaPhi = extractUncert(FitParIndex::phi) / 1._degree;
0161 sigmaT0 = extractUncert(FitParIndex::t0) / 1._ns;
0162
0163 chi2 = result.chi2;
0164 nDoF = result.nDoF;
0165 nIter = result.nIter;
0166
0167 outTree->Fill();
0168 if ((evt + 1) % 10000 == 0u) {
0169 ACTS_INFO("Processed " << (evt + 1) << "/" << nEvents
0170 << " events. Test: " << outTree->GetName());
0171 }
0172 }
0173
0174 TimePoint_t end = std::chrono::system_clock::now();
0175 auto diff = std::chrono::duration_cast<std::chrono::milliseconds>(end - start)
0176 .count();
0177 std::unique_lock guard{writeMutex};
0178 outFile.WriteObject(outTree.get(), outTree->GetName());
0179 ACTS_INFO("Test " << outTree->GetName() << " finished. " << goodFits
0180 << " tracks written. It took " << (diff / 1000)
0181 << " seconds.");
0182 return diff;
0183 }
0184 #undef DECLARE_BRANCH
0185
0186 BOOST_AUTO_TEST_SUITE(StrawLineFitTestSuite)
0187
0188 BOOST_AUTO_TEST_CASE(SimpleLineFit) {
0189 using namespace std::chrono_literals;
0190
0191 auto outFile =
0192 std::make_unique<TFile>("StrawLineFitterTest.root", "RECREATE");
0193
0194 Fitter_t::Config fitCfg{};
0195 fitCfg.useHessian = false;
0196 fitCfg.calcAlongStraw = true;
0197 fitCfg.recalibrate = false;
0198 fitCfg.useFastFitter = false;
0199 fitCfg.ranges[toUnderlying(FitParIndex::theta)] =
0200 std::array{1._degree, 179._degree};
0201 fitCfg.ranges[toUnderlying(FitParIndex::phi)] =
0202 std::array{-179._degree, 179._degree};
0203 fitCfg.ranges[toUnderlying(FitParIndex::x0)] = std::array{-1000., 1000.};
0204 fitCfg.ranges[toUnderlying(FitParIndex::y0)] = std::array{-1000., 1000.};
0205
0206 Fitter_t::Config fastPreCfg{fitCfg};
0207 fastPreCfg.useFastFitter = true;
0208
0209 Fitter_t::Config fastCfg{fastPreCfg};
0210 fastCfg.fastPreFitter = false;
0211
0212
0213 std::vector<std::pair<std::string, std::future<long int>>> timings{};
0214 using namespace std::chrono_literals;
0215
0216 auto sendSleep = [&timings]() {
0217 do {
0218 std::this_thread::sleep_for(100ms);
0219 } while (std::ranges::count_if(timings, [](const auto& timeObj) {
0220 return timeObj.second.wait_for(0ms) != std::future_status::ready;
0221 }) >= nThreads);
0222 };
0223
0224
0225
0226
0227
0228
0229
0230
0231
0232
0233 auto launchTest = [&](const std::string& testName, const GenCfg_t& genCfg,
0234 const unsigned seed) {
0235 sendSleep();
0236 timings.emplace_back(
0237 "Fast" + testName, std::async(std::launch::async, [&]() {
0238 return runFitTest(fastCfg, genCfg, "Fast" + testName, seed, *outFile);
0239 }));
0240 sendSleep();
0241 timings.emplace_back(testName, std::async(std::launch::async, [&]() {
0242 return runFitTest(fitCfg, genCfg, testName, seed,
0243 *outFile);
0244 }));
0245
0246 sendSleep();
0247 timings.emplace_back(
0248 "FastPre" + testName, std::async(std::launch::async, [&]() {
0249 return runFitTest(fastPreCfg, genCfg, "FastPre" + testName, seed,
0250 *outFile);
0251 }));
0252 sendSleep();
0253 };
0254 {
0255 GenCfg_t genCfg{};
0256 genCfg.twinStraw = false;
0257 genCfg.createStrips = false;
0258 launchTest("StrawOnlyTest", genCfg, 1602);
0259 }
0260
0261 {
0262 GenCfg_t genCfg{};
0263 genCfg.createStraws = true;
0264 genCfg.twinStraw = true;
0265 genCfg.createStrips = false;
0266
0267 launchTest("StrawAndTwinTest", genCfg, 1503);
0268 }
0269
0270 {
0271 GenCfg_t genCfg{};
0272 genCfg.createStrips = true;
0273 genCfg.twinStraw = false;
0274 genCfg.combineSpacePoints = false;
0275 genCfg.discretizeStrips = true;
0276 genCfg.createStraws = true;
0277 genCfg.stripPitchLoc1 = 2._cm;
0278 genCfg.stripPitchLoc0 = 3.5_cm;
0279 launchTest("StrawAndStripTest", genCfg, 1701);
0280 }
0281
0282 {
0283 GenCfg_t genCfg{};
0284
0285 genCfg.createStrips = true;
0286 genCfg.twinStraw = false;
0287 genCfg.combineSpacePoints = true;
0288 genCfg.discretizeStrips = true;
0289 genCfg.createStraws = true;
0290 genCfg.stripPitchLoc1 = 2._cm;
0291 genCfg.stripPitchLoc0 = 3.5_cm;
0292 launchTest("StrawAndStrip2DTest", genCfg, 1404);
0293 }
0294
0295 {
0296 GenCfg_t genCfg{};
0297 genCfg.createStrips = true;
0298 genCfg.twinStraw = false;
0299 genCfg.combineSpacePoints = false;
0300 genCfg.discretizeStrips = true;
0301 genCfg.createStraws = false;
0302 genCfg.stripPitchLoc1 = 500._um;
0303 genCfg.stripPitchLoc0 = 3._cm;
0304 launchTest("StripOnlyTest", genCfg, 2070);
0305 }
0306
0307 {
0308 GenCfg_t genCfg{};
0309 genCfg.createStrips = true;
0310 genCfg.twinStraw = false;
0311 genCfg.combineSpacePoints = true;
0312 genCfg.discretizeStrips = true;
0313 genCfg.createStraws = false;
0314 genCfg.stripPitchLoc1 = 500._um;
0315 genCfg.stripPitchLoc0 = 3._cm;
0316 launchTest("Strip2DOnlyTest", genCfg, 2225);
0317 }
0318
0319 {
0320 GenCfg_t genCfg{};
0321 genCfg.createStrips = true;
0322 genCfg.twinStraw = false;
0323 genCfg.combineSpacePoints = false;
0324 genCfg.discretizeStrips = true;
0325 genCfg.createStraws = false;
0326 genCfg.stripPitchLoc1 = 500._um;
0327 genCfg.stripDirLoc0.clear();
0328 genCfg.stripDirLoc1 = {
0329 makeDirectionFromPhiTheta(0._degree, 90._degree),
0330 makeDirectionFromPhiTheta(0._degree, 90._degree),
0331 makeDirectionFromPhiTheta(-1.5_degree, 90._degree),
0332 makeDirectionFromPhiTheta(1.5_degree, 90._degree),
0333 makeDirectionFromPhiTheta(-1.5_degree, 90._degree),
0334 makeDirectionFromPhiTheta(1.5_degree, 90._degree),
0335 makeDirectionFromPhiTheta(0._degree, 90._degree),
0336 makeDirectionFromPhiTheta(0._degree, 90._degree),
0337
0338 };
0339
0340 fitCfg.parsToUse = {FitParIndex::x0, FitParIndex::y0, FitParIndex::theta,
0341 FitParIndex::phi};
0342 launchTest("StereoStripTest", genCfg, 1800);
0343 }
0344
0345 while (std::ranges::any_of(timings, [](const auto& lblTh) {
0346 return lblTh.second.wait_for(0ms) != std::future_status::ready;
0347 })) {
0348 std::this_thread::sleep_for(10ms);
0349 }
0350 {
0351 auto timeHisto = std::make_unique<TH1D>("TestTimings", "timings",
0352 timings.size(), 0, timings.size());
0353 int bin{1};
0354 for (auto& [label, result] : timings) {
0355 timeHisto->GetXaxis()->SetBinLabel(bin, label.c_str());
0356 timeHisto->SetBinContent(bin, static_cast<double>(result.get()) /
0357 static_cast<double>(nEvents));
0358 ++bin;
0359 }
0360 outFile->WriteObject(timeHisto.get(), timeHisto->GetName());
0361 }
0362 }
0363
0364 BOOST_AUTO_TEST_SUITE_END()
0365 }