File indexing completed on 2026-05-27 07:23:20
0001
0002
0003
0004
0005
0006
0007
0008
0009 #pragma once
0010
0011 #include "ActsAlignment/Kernel/Alignment.hpp"
0012
0013 #include "Acts/EventData/VectorMultiTrajectory.hpp"
0014 #include "Acts/EventData/VectorTrackContainer.hpp"
0015 #include "Acts/TrackFitting/detail/KalmanGlobalCovariance.hpp"
0016 #include "Acts/Utilities/Logger.hpp"
0017 #include "Acts/Utilities/detail/EigenCompat.hpp"
0018 #include "ActsAlignment/Kernel/AlignmentError.hpp"
0019 #include "ActsAlignment/Kernel/detail/AlignmentEngine.hpp"
0020
0021 #include <queue>
0022
0023 template <typename fitter_t>
0024 template <typename source_link_t, typename fit_options_t>
0025 Acts::Result<ActsAlignment::detail::TrackAlignmentState>
0026 ActsAlignment::Alignment<fitter_t>::evaluateTrackAlignmentState(
0027 const Acts::GeometryContext& gctx,
0028 const std::vector<source_link_t>& sourceLinks,
0029 const Acts::BoundTrackParameters& sParameters,
0030 const fit_options_t& fitOptions,
0031 const std::unordered_map<const Acts::Surface*, std::size_t>&
0032 idxedAlignSurfaces,
0033 const ActsAlignment::AlignmentMask& alignMask) const {
0034 Acts::TrackContainer tracks{Acts::VectorTrackContainer{},
0035 Acts::VectorMultiTrajectory{}};
0036
0037
0038 Acts::SourceLinkAdapterIterator begin{sourceLinks.begin()};
0039 Acts::SourceLinkAdapterIterator end{sourceLinks.end()};
0040
0041
0042 auto fitRes = m_fitter.fit(begin, end, sParameters, fitOptions, tracks);
0043
0044 if (!fitRes.ok()) {
0045 ACTS_WARNING("Fit failure");
0046 return fitRes.error();
0047 }
0048
0049 const auto& track = fitRes.value();
0050
0051 const auto& globalTrackParamsCov =
0052 Acts::detail::globalTrackParametersCovariance(
0053 tracks.trackStateContainer(), track.tipIndex());
0054
0055 const auto alignState = detail::trackAlignmentState(
0056 gctx, tracks.trackStateContainer(), track.tipIndex(),
0057 globalTrackParamsCov, idxedAlignSurfaces, alignMask);
0058 if (alignState.alignmentDof == 0) {
0059 ACTS_VERBOSE("No alignment dof on track!");
0060 return AlignmentError::NoAlignmentDofOnTrack;
0061 }
0062 return alignState;
0063 }
0064
0065 template <typename fitter_t>
0066 template <typename trajectory_container_t,
0067 typename start_parameters_container_t, typename fit_options_t>
0068 void ActsAlignment::Alignment<fitter_t>::calculateAlignmentParameters(
0069 const trajectory_container_t& trajectoryCollection,
0070 const start_parameters_container_t& startParametersCollection,
0071 const fit_options_t& fitOptions,
0072 ActsAlignment::AlignmentResult& alignResult,
0073 const ActsAlignment::AlignmentMask& alignMask) const {
0074
0075
0076 assert(trajectoryCollection.size() == startParametersCollection.size());
0077
0078
0079 alignResult.alignmentDof =
0080 alignResult.idxedAlignSurfaces.size() * Acts::eAlignmentSize;
0081
0082 fit_options_t fitOptionsWithRefSurface = fitOptions;
0083
0084
0085 alignResult.chi2 = 0;
0086 alignResult.measurementDim = 0;
0087 alignResult.numTracks = trajectoryCollection.size();
0088 std::vector<detail::TrackAlignmentState> alignmentStates;
0089 for (unsigned int iTraj = 0; iTraj < trajectoryCollection.size(); iTraj++) {
0090 const auto& sourceLinks = trajectoryCollection.at(iTraj);
0091 const auto& sParameters = startParametersCollection.at(iTraj);
0092
0093 fitOptionsWithRefSurface.referenceSurface = &sParameters.referenceSurface();
0094
0095 auto evaluateRes = evaluateTrackAlignmentState(
0096 fitOptions.geoContext, sourceLinks, sParameters,
0097 fitOptionsWithRefSurface, alignResult.idxedAlignSurfaces, alignMask);
0098 if (!evaluateRes.ok()) {
0099 ACTS_DEBUG("Evaluation of alignment state for track " << iTraj
0100 << " failed");
0101 continue;
0102 }
0103 const auto& alignState = evaluateRes.value();
0104 alignmentStates.push_back(alignState);
0105 }
0106 return calculateAlignmentParameters(alignmentStates, alignResult);
0107 }
0108
0109 template <typename fitter_t>
0110 void ActsAlignment::Alignment<fitter_t>::calculateAlignmentParameters(
0111 const std::vector<detail::TrackAlignmentState>& trackAlignmentStates,
0112 AlignmentResult& alignResult) const {
0113
0114 alignResult.alignmentDof =
0115 alignResult.idxedAlignSurfaces.size() * Acts::eAlignmentSize;
0116
0117 alignResult.sumChi2Derivative =
0118 Acts::DynamicVector::Zero(alignResult.alignmentDof);
0119 alignResult.sumChi2SecondDerivative = Acts::DynamicMatrix::Zero(
0120 alignResult.alignmentDof, alignResult.alignmentDof);
0121 alignResult.chi2 = 0;
0122 alignResult.measurementDim = 0;
0123 alignResult.numTracks = trackAlignmentStates.size();
0124 double sumChi2ONdf = 0;
0125 for (const auto& alignState : trackAlignmentStates) {
0126 for (const auto& [rowSurface, rows] : alignState.alignedSurfaces) {
0127 const auto& [dstRow, srcRow] = rows;
0128
0129 alignResult.sumChi2Derivative.segment<Acts::eAlignmentSize>(
0130 dstRow * Acts::eAlignmentSize) +=
0131 alignState.alignmentToChi2Derivative.segment(
0132 srcRow * Acts::eAlignmentSize, Acts::eAlignmentSize);
0133
0134 for (const auto& [colSurface, cols] : alignState.alignedSurfaces) {
0135 const auto& [dstCol, srcCol] = cols;
0136 alignResult.sumChi2SecondDerivative
0137 .block<Acts::eAlignmentSize, Acts::eAlignmentSize>(
0138 dstRow * Acts::eAlignmentSize, dstCol * Acts::eAlignmentSize) +=
0139 alignState.alignmentToChi2SecondDerivative.block(
0140 srcRow * Acts::eAlignmentSize, srcCol * Acts::eAlignmentSize,
0141 Acts::eAlignmentSize, Acts::eAlignmentSize);
0142 }
0143 }
0144 alignResult.chi2 += alignState.chi2;
0145 alignResult.measurementDim += alignState.measurementDim;
0146 sumChi2ONdf += alignState.chi2 / alignState.measurementDim;
0147 }
0148 alignResult.averageChi2ONdf = sumChi2ONdf / alignResult.numTracks;
0149
0150
0151
0152
0153 std::size_t alignDof = alignResult.alignmentDof;
0154 Acts::DynamicMatrix sumChi2SecondDerivativeInverse =
0155 Acts::DynamicMatrix::Zero(alignDof, alignDof);
0156 sumChi2SecondDerivativeInverse =
0157 alignResult.sumChi2SecondDerivative.inverse();
0158 if (sumChi2SecondDerivativeInverse.hasNaN()) {
0159 ACTS_DEBUG("Chi2 second derivative inverse has NaN");
0160 }
0161
0162
0163 alignResult.deltaAlignmentParameters = Acts::DynamicVector::Zero(alignDof);
0164 alignResult.alignmentCovariance =
0165 Acts::DynamicMatrix::Zero(alignDof, alignDof);
0166
0167 alignResult.deltaAlignmentParameters =
0168 -alignResult.sumChi2SecondDerivative.fullPivLu().solve(
0169 alignResult.sumChi2Derivative);
0170 ACTS_VERBOSE("sumChi2SecondDerivative = \n"
0171 << alignResult.sumChi2SecondDerivative);
0172 ACTS_VERBOSE("sumChi2Derivative = \n" << alignResult.sumChi2Derivative);
0173 ACTS_VERBOSE("alignResult.deltaAlignmentParameters \n");
0174
0175
0176 alignResult.alignmentCovariance = 2 * sumChi2SecondDerivativeInverse;
0177
0178 alignResult.deltaChi2 = 0.5 * alignResult.sumChi2Derivative.transpose() *
0179 alignResult.deltaAlignmentParameters;
0180 }
0181
0182 template <typename fitter_t>
0183 double ActsAlignment::Alignment<fitter_t>::decompositionAnalysis(
0184 const AlignmentResult& res, std::ostream& out) {
0185 if (res.sumChi2SecondDerivative.cols() == 0) {
0186 ACTS_ERROR(
0187 "Please run Alignment::calculateAlignmentParameters before calling "
0188 "Alignment::decompositionAnalysis.");
0189 return -1;
0190 }
0191 Eigen::SelfAdjointEigenSolver<Acts::DynamicMatrix> eigenSolver(
0192 res.sumChi2SecondDerivative);
0193 if (eigenSolver.info() != Eigen::Success) {
0194 std::cout << " FAILED to find decompose correlation term" << std::endl;
0195 return -1;
0196 }
0197 const Acts::DynamicVector eigenVals = eigenSolver.eigenvalues();
0198 const Acts::DynamicMatrix eigenVecs = eigenSolver.eigenvectors();
0199
0200 std::map<double, int> sortedEV;
0201 for (int k = 0; k < eigenVals.size(); ++k) {
0202 sortedEV.emplace(eigenVals(k), k);
0203 }
0204 double firstEV = -1, lastEV = -1;
0205 for (auto& [EV, index] : sortedEV) {
0206 if (EV > 0 && firstEV < 0) {
0207 firstEV = EV;
0208 }
0209 lastEV = EV;
0210 out << " Eigenvector " << index << " has eigenvalue " << EV << std::endl;
0211 for (std::size_t row = 0; row < eigenVecs.rows(); ++row) {
0212 out << " " << std::setw(12) << " " << std::setw(3) << row + 1
0213 << " " << std::setw(12) << eigenVecs(row, index) << std::endl;
0214 }
0215 out << std::endl;
0216 }
0217 return lastEV / firstEV;
0218 }
0219
0220 template <typename fitter_t>
0221 Acts::Result<void>
0222 ActsAlignment::Alignment<fitter_t>::updateAlignmentParameters(
0223 const Acts::GeometryContext& gctx,
0224 const std::vector<Acts::SurfacePlacementBase*>& alignedDetElements,
0225 const ActsAlignment::AlignedTransformUpdaterConcept auto&
0226 alignedTransformUpdater,
0227 ActsAlignment::AlignmentResult& alignResult) const {
0228
0229 Acts::AlignmentVector deltaAlignmentParam = Acts::AlignmentVector::Zero();
0230 for (const auto& [surface, index] : alignResult.idxedAlignSurfaces) {
0231
0232 const Acts::Vector3& oldCenter = surface->center(gctx);
0233 const Acts::Transform3& oldTransform =
0234 surface->localToGlobalTransform(gctx);
0235
0236
0237 deltaAlignmentParam = alignResult.deltaAlignmentParameters.segment(
0238 Acts::eAlignmentSize * index, Acts::eAlignmentSize);
0239
0240 Acts::Vector3 deltaCenter =
0241 deltaAlignmentParam.segment<3>(Acts::eAlignmentCenter0);
0242
0243 Acts::Vector3 deltaEulerAngles =
0244 deltaAlignmentParam.segment<3>(Acts::eAlignmentRotation0);
0245
0246
0247 const Acts::Vector3 newCenter = oldCenter + deltaCenter;
0248 Acts::Transform3 newTransform = oldTransform;
0249 newTransform.translation() = newCenter;
0250
0251
0252
0253 newTransform *=
0254 Acts::AngleAxis3(deltaEulerAngles(2), Acts::Vector3::UnitZ());
0255 newTransform *=
0256 Acts::AngleAxis3(deltaEulerAngles(1), Acts::Vector3::UnitY());
0257 newTransform *=
0258 Acts::AngleAxis3(deltaEulerAngles(0), Acts::Vector3::UnitX());
0259
0260
0261
0262
0263 ACTS_VERBOSE("Delta of alignment parameters at element "
0264 << index << "= \n"
0265 << deltaAlignmentParam);
0266 bool updated = alignedTransformUpdater(alignedDetElements.at(index), gctx,
0267 newTransform);
0268 if (!updated) {
0269 ACTS_ERROR("Update alignment parameters for detector element failed");
0270 return AlignmentError::AlignmentParametersUpdateFailure;
0271 }
0272 }
0273
0274 return Acts::Result<void>::success();
0275 }
0276
0277 template <typename fitter_t>
0278 template <typename trajectory_container_t,
0279 typename start_parameters_container_t, typename fit_options_t>
0280 Acts::Result<ActsAlignment::AlignmentResult>
0281 ActsAlignment::Alignment<fitter_t>::align(
0282 const trajectory_container_t& trajectoryCollection,
0283 const start_parameters_container_t& startParametersCollection,
0284 const ActsAlignment::AlignmentOptions<fit_options_t>& alignOptions) const {
0285
0286 AlignmentResult alignResult;
0287
0288
0289 for (unsigned int iDetElement = 0;
0290 iDetElement < alignOptions.alignedDetElements.size(); iDetElement++) {
0291 alignResult.idxedAlignSurfaces.emplace(
0292 &alignOptions.alignedDetElements.at(iDetElement)->surface(),
0293 iDetElement);
0294 }
0295 ACTS_VERBOSE("There are " << alignResult.idxedAlignSurfaces.size()
0296 << " detector elements to be aligned");
0297
0298
0299 bool converged = false;
0300 bool alignmentParametersUpdated = false;
0301 std::queue<double> recentChi2ONdf;
0302 ACTS_INFO("Max number of iterations: " << alignOptions.maxIterations);
0303 for (unsigned int iIter = 0; iIter < alignOptions.maxIterations; iIter++) {
0304
0305
0306 AlignmentMask alignMask = AlignmentMask::All;
0307
0308 auto iter_it = alignOptions.iterationState.find(iIter);
0309 if (iter_it != alignOptions.iterationState.end()) {
0310 alignMask = iter_it->second;
0311 }
0312
0313 calculateAlignmentParameters(
0314 trajectoryCollection, startParametersCollection,
0315 alignOptions.fitOptions, alignResult, alignMask);
0316
0317 ACTS_INFO("iIter = " << iIter << ", total chi2 = " << alignResult.chi2
0318 << ", total measurementDim = "
0319 << alignResult.measurementDim
0320 << " and average chi2/ndf = "
0321 << alignResult.averageChi2ONdf);
0322
0323
0324
0325 if (recentChi2ONdf.size() >=
0326 alignOptions.deltaAverageChi2ONdfCutOff.first) {
0327 if (std::abs(recentChi2ONdf.front() - alignResult.averageChi2ONdf) <=
0328 alignOptions.deltaAverageChi2ONdfCutOff.second) {
0329 ACTS_INFO(
0330 "Alignment has converged with change of chi2/ndf < "
0331 << alignOptions.deltaAverageChi2ONdfCutOff.second << " in the last "
0332 << alignOptions.deltaAverageChi2ONdfCutOff.first << " iterations"
0333 << " after " << iIter << " iteration(s)");
0334 converged = true;
0335 break;
0336 }
0337 recentChi2ONdf.pop();
0338 }
0339
0340 if (alignResult.averageChi2ONdf <= alignOptions.averageChi2ONdfCutOff) {
0341 ACTS_INFO("Alignment has converged with average chi2/ndf < "
0342 << alignOptions.averageChi2ONdfCutOff << " after " << iIter
0343 << " iteration(s)");
0344 converged = true;
0345 break;
0346 }
0347
0348
0349 recentChi2ONdf.push(alignResult.averageChi2ONdf);
0350
0351 ACTS_INFO("The solved delta of alignmentParameters = \n "
0352 << alignResult.deltaAlignmentParameters);
0353
0354 auto updateRes = updateAlignmentParameters(
0355 alignOptions.fitOptions.geoContext, alignOptions.alignedDetElements,
0356 alignOptions.alignedTransformUpdater, alignResult);
0357 if (!updateRes.ok()) {
0358 ACTS_ERROR("Update alignment parameters failed: " << updateRes.error());
0359 return updateRes.error();
0360 }
0361 alignmentParametersUpdated = true;
0362 }
0363
0364
0365 if (!converged) {
0366 ACTS_ERROR("Alignment is not converged.");
0367 alignResult.result = AlignmentError::ConvergeFailure;
0368 }
0369
0370
0371
0372 if (alignmentParametersUpdated) {
0373 for (const auto& det : alignOptions.alignedDetElements) {
0374 const auto& surface = &det->surface();
0375 const auto& transform =
0376 det->localToGlobalTransform(alignOptions.fitOptions.geoContext);
0377
0378 alignResult.alignedParameters.emplace(det, transform);
0379 const auto& translation = transform.translation();
0380 const auto& rotation = transform.rotation();
0381 const Acts::Vector3 rotAngles =
0382 Acts::detail::EigenCompat::canonicalEulerAngles(rotation, 2, 1, 0);
0383 ACTS_VERBOSE("Detector element with surface "
0384 << surface->geometryId()
0385 << " has aligned geometry position as below:");
0386 ACTS_VERBOSE("Center (cenX, cenY, cenZ) = " << translation.transpose());
0387 ACTS_VERBOSE(
0388 "Euler angles (rotZ, rotY, rotX) = " << rotAngles.transpose());
0389 ACTS_VERBOSE("Rotation matrix = \n" << rotation);
0390 }
0391 } else {
0392 ACTS_DEBUG("Alignment parameters is not updated.");
0393 }
0394
0395 return alignResult;
0396 }