File indexing completed on 2026-09-23 08:20:33
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include "ActsExamples/Validation/ResPlotTool.hpp"
0010
0011 #include "Acts/Definitions/Algebra.hpp"
0012 #include "Acts/Definitions/TrackParametrization.hpp"
0013 #include "Acts/Surfaces/Surface.hpp"
0014 #include "Acts/Utilities/Intersection.hpp"
0015 #include "Acts/Utilities/Result.hpp"
0016 #include "Acts/Utilities/detail/periodic.hpp"
0017
0018 #include <cmath>
0019 #include <cstdint>
0020 #include <format>
0021 #include <numbers>
0022 #include <stdexcept>
0023
0024 namespace ActsExamples {
0025
0026 static constexpr double nan = std::numeric_limits<double>::quiet_NaN();
0027
0028
0029
0030 static double wrapResidual(std::size_t index, double residual) {
0031 if (index != Acts::eBoundPhi) {
0032 return residual;
0033 }
0034 return Acts::detail::difference_periodic(residual, 0., 2 * std::numbers::pi);
0035 }
0036
0037 ResPlotTool::ResPlotTool(const ResPlotTool::Config& cfg,
0038 Acts::Logging::Level lvl)
0039 : m_cfg(cfg), m_logger(Acts::getDefaultLogger("ResPlotTool", lvl)) {
0040
0041 const auto binning = [this](const std::string& key) -> const AxisVariant& {
0042 const auto it = m_cfg.varBinning.find(key);
0043 if (it == m_cfg.varBinning.end()) {
0044 throw std::invalid_argument("ResPlotTool: missing binning for '" + key +
0045 "'");
0046 }
0047 return it->second;
0048 };
0049
0050 const auto& etaAxis = binning("Eta");
0051 const auto& phiAxis = binning("Phi");
0052 const auto& ptAxis = binning("Pt");
0053 const auto& pullAxis = binning("Pull");
0054
0055 if (m_cfg.paramNames.size() != Acts::eBoundSize) {
0056 throw std::invalid_argument(
0057 "ResPlotTool: expected one name per bound parameter, the caller has to "
0058 "fill in the empty default");
0059 }
0060
0061 ACTS_DEBUG("Initialize the histograms for residual and pull plots");
0062
0063 std::vector<std::string> allParamNames = m_cfg.paramNames;
0064 allParamNames.push_back(m_cfg.qOverPtName);
0065 allParamNames.push_back(m_cfg.relQoverPtName);
0066
0067 for (const std::string& parName : allParamNames) {
0068 const auto& residualAxis = binning("Residual_" + parName);
0069
0070
0071 m_res.emplace(parName, Acts::Experimental::Histogram1(
0072 std::format("res_{}", parName),
0073 std::format("Residual of {}", parName),
0074 std::array{residualAxis}));
0075
0076
0077 m_resVsEta.emplace(parName,
0078 Acts::Experimental::Histogram2(
0079 std::format("res_{}_vs_eta", parName),
0080 std::format("Residual of {} vs eta", parName),
0081 std::array{etaAxis, residualAxis}));
0082
0083
0084 m_resVsPt.emplace(parName, Acts::Experimental::Histogram2(
0085 std::format("res_{}_vs_pT", parName),
0086 std::format("Residual of {} vs pT", parName),
0087 std::array{ptAxis, residualAxis}));
0088
0089
0090 m_resVsEtaPhi.emplace(parName,
0091 Acts::Experimental::Histogram3(
0092 std::format("res_{}_vs_eta_phi", parName),
0093 std::format("Residual of {} vs eta-phi", parName),
0094 std::array{etaAxis, phiAxis, residualAxis}));
0095
0096
0097 m_resVsEtaPt.emplace(parName,
0098 Acts::Experimental::Histogram3(
0099 std::format("res_{}_vs_eta_pT", parName),
0100 std::format("Residual of {} vs eta-pT", parName),
0101 std::array{etaAxis, ptAxis, residualAxis}));
0102
0103
0104 m_pull.emplace(
0105 parName, Acts::Experimental::Histogram1(
0106 std::format("pull_{}", parName),
0107 std::format("Pull of {}", parName), std::array{pullAxis}));
0108
0109
0110 m_pullVsEta.emplace(parName, Acts::Experimental::Histogram2(
0111 std::format("pull_{}_vs_eta", parName),
0112 std::format("Pull of {} vs eta", parName),
0113 std::array{etaAxis, pullAxis}));
0114
0115
0116 m_pullVsPt.emplace(parName, Acts::Experimental::Histogram2(
0117 std::format("pull_{}_vs_pT", parName),
0118 std::format("Pull of {} vs pT", parName),
0119 std::array{ptAxis, pullAxis}));
0120
0121
0122 m_pullVsEtaPhi.emplace(parName,
0123 Acts::Experimental::Histogram3(
0124 std::format("pull_{}_vs_eta_phi", parName),
0125 std::format("Pull of {} vs eta-phi", parName),
0126 std::array{etaAxis, phiAxis, pullAxis}));
0127
0128
0129 m_pullVsEtaPt.emplace(parName,
0130 Acts::Experimental::Histogram3(
0131 std::format("pull_{}_vs_eta_pT", parName),
0132 std::format("Pull of {} vs eta-pT", parName),
0133 std::array{etaAxis, ptAxis, pullAxis}));
0134 }
0135 }
0136
0137 void ResPlotTool::fill(const Acts::GeometryContext& gctx,
0138 const SimParticleState& truthParticle,
0139 const Acts::BoundTrackParameters& fittedParamters) {
0140 using Acts::VectorHelpers::eta;
0141 using Acts::VectorHelpers::perp;
0142 using Acts::VectorHelpers::phi;
0143 using Acts::VectorHelpers::theta;
0144
0145 using enum Acts::BoundIndices;
0146
0147
0148 const Acts::Surface& pSurface = fittedParamters.referenceSurface();
0149
0150
0151 Acts::BoundVector truthParameters = Acts::BoundVector::Zero();
0152 const Acts::Intersection3D intersection =
0153 pSurface
0154 .intersect(gctx, truthParticle.position(), truthParticle.direction())
0155 .closest();
0156 if (intersection.isValid()) {
0157 const Acts::Result<Acts::Vector2> lpResult = pSurface.globalToLocal(
0158 gctx, intersection.position(), truthParticle.direction());
0159 assert(lpResult.ok());
0160
0161 truthParameters[eBoundLoc0] = lpResult.value()[eBoundLoc0];
0162 truthParameters[eBoundLoc1] = lpResult.value()[eBoundLoc1];
0163 } else {
0164 ACTS_ERROR("Cannot get the truth perigee parameter");
0165 }
0166 truthParameters[eBoundPhi] = phi(truthParticle.direction());
0167 truthParameters[eBoundTheta] = theta(truthParticle.direction());
0168 truthParameters[eBoundQOverP] = truthParticle.qOverP();
0169 truthParameters[eBoundTime] = truthParticle.time();
0170
0171
0172
0173 fill(truthParameters,
0174 Binning{eta(truthParticle.direction()), phi(truthParticle.direction()),
0175 truthParticle.transverseMomentum()},
0176 truthParticle.charge(), truthParticle.absoluteCharge(), fittedParamters);
0177 }
0178
0179 void ResPlotTool::fill(const Acts::BoundTrackParameters& truthParameters,
0180 const Acts::BoundTrackParameters& fittedParameters) {
0181 using Acts::VectorHelpers::eta;
0182 using Acts::VectorHelpers::phi;
0183
0184 if (truthParameters.referenceSurface() !=
0185 fittedParameters.referenceSurface()) {
0186 throw std::invalid_argument(
0187 "ResPlotTool: truth and fitted parameters are expressed on different "
0188 "reference surfaces");
0189 }
0190
0191 const double truthCharge = truthParameters.charge();
0192 fill(truthParameters.parameters(),
0193 Binning{eta(truthParameters.direction()),
0194 phi(truthParameters.direction()),
0195 truthParameters.transverseMomentum()},
0196 truthCharge, std::abs(truthCharge), fittedParameters);
0197 }
0198
0199 void ResPlotTool::fill(const Acts::BoundVector& truthVector,
0200 const Binning& binning, double truthCharge,
0201 double truthAbsCharge,
0202 const Acts::BoundTrackParameters& fittedParameters) {
0203 using enum Acts::BoundIndices;
0204
0205 const double truthEta = binning.eta;
0206 const double truthPhi = binning.phi;
0207 const double truthPt = binning.pt;
0208
0209
0210 const Acts::BoundVector& trackParameters = fittedParameters.parameters();
0211 const Acts::BoundMatrix& trackCovariance =
0212 fittedParameters.covariance().value_or(Acts::BoundMatrix::Zero());
0213
0214
0215 for (unsigned int paramId = 0; paramId < Acts::eBoundSize; paramId++) {
0216 const std::string& parName = m_cfg.paramNames.at(paramId);
0217
0218 const double residual =
0219 wrapResidual(paramId, trackParameters[paramId] - truthVector[paramId]);
0220 fillResidual(parName, residual, truthEta, truthPhi, truthPt);
0221
0222 const double var = trackCovariance(paramId, paramId);
0223
0224 const double pull = var > 0 ? residual / std::sqrt(var) : nan;
0225 fillPull(parName, pull, truthEta, truthPhi, truthPt);
0226 }
0227
0228
0229 {
0230 const double truthQoverPt = truthCharge / truthPt;
0231 const double truthPtOverAbsQ = truthPt / truthAbsCharge;
0232 const double recoQoverPt =
0233 trackParameters[eBoundQOverP] / std::sin(trackParameters[eBoundTheta]);
0234 const double residualQoverPt = recoQoverPt - truthQoverPt;
0235 fillResidual(m_cfg.qOverPtName, residualQoverPt, truthEta, truthPhi,
0236 truthPt);
0237
0238 const double residualRelQoverPt = truthPtOverAbsQ * residualQoverPt;
0239 fillResidual(m_cfg.relQoverPtName, residualRelQoverPt, truthEta, truthPhi,
0240 truthPt);
0241
0242 const double covarianceQoverPt = [&]() -> double {
0243 const Acts::Vector2 jacobian{
0244 -recoQoverPt / std::tan(trackParameters[eBoundTheta]),
0245 1 / std::sin(trackParameters[eBoundTheta])};
0246 const Acts::SquareMatrix2 covariance = trackCovariance(
0247 {eBoundTheta, eBoundQOverP}, {eBoundTheta, eBoundQOverP});
0248 return jacobian.transpose() * covariance * jacobian;
0249 }();
0250 const double covarianceRelQoverPt =
0251 Acts::square(truthPtOverAbsQ) * covarianceQoverPt;
0252
0253 const double pullQoverPt =
0254 covarianceQoverPt > 0 ? residualQoverPt / std::sqrt(covarianceQoverPt)
0255 : nan;
0256 fillPull(m_cfg.qOverPtName, pullQoverPt, truthEta, truthPhi, truthPt);
0257
0258 const double pullRelQoverPt =
0259 covarianceRelQoverPt > 0
0260 ? residualRelQoverPt / std::sqrt(covarianceRelQoverPt)
0261 : nan;
0262 fillPull(m_cfg.relQoverPtName, pullRelQoverPt, truthEta, truthPhi, truthPt);
0263 }
0264 }
0265
0266 void ResPlotTool::fill(const Binning& binning,
0267 const Acts::VariableBoundSubspaceHelper& subspace,
0268 const Acts::BoundVector& residuals,
0269 const Acts::BoundMatrix& residualCovariance) {
0270 for (const std::uint8_t index : subspace) {
0271 const std::string& parName = m_cfg.paramNames.at(index);
0272
0273 const double residual = wrapResidual(index, residuals[index]);
0274 fillResidual(parName, residual, binning.eta, binning.phi, binning.pt);
0275
0276
0277 const double var = residualCovariance(index, index);
0278 const double pull = var > 0 ? residual / std::sqrt(var) : nan;
0279 fillPull(parName, pull, binning.eta, binning.phi, binning.pt);
0280 }
0281 }
0282
0283 void ResPlotTool::fillResidual(const std::string& paramName, double residual,
0284 double truthEta, double truthPhi,
0285 double truthPt) {
0286 m_res.at(paramName).fill({residual});
0287 m_resVsEta.at(paramName).fill({truthEta, residual});
0288 m_resVsPt.at(paramName).fill({truthPt, residual});
0289 m_resVsEtaPhi.at(paramName).fill({truthEta, truthPhi, residual});
0290 m_resVsEtaPt.at(paramName).fill({truthEta, truthPt, residual});
0291 }
0292
0293 void ResPlotTool::fillPull(const std::string& paramName, double pull,
0294 double truthEta, double truthPhi, double truthPt) {
0295 m_pull.at(paramName).fill({pull});
0296 m_pullVsEta.at(paramName).fill({truthEta, pull});
0297 m_pullVsPt.at(paramName).fill({truthPt, pull});
0298 m_pullVsEtaPhi.at(paramName).fill({truthEta, truthPhi, pull});
0299 m_pullVsEtaPt.at(paramName).fill({truthEta, truthPt, pull});
0300 }
0301
0302 }