Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2026-07-16 08:08:51

0001 // This file is part of the ACTS project.
0002 //
0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
0008 
0009 #pragma once
0010 
0011 #include "Acts/Definitions/Units.hpp"
0012 #include "Acts/Seeding/CompositeSpacePointLineFitter.hpp"
0013 #include "Acts/Seeding/CompositeSpacePointLineSeeder.hpp"
0014 #include "Acts/Utilities/Enumerate.hpp"
0015 #include "Acts/Utilities/Logger.hpp"
0016 #include "Acts/Utilities/StringHelpers.hpp"
0017 #include "Acts/Utilities/UnitVectors.hpp"
0018 #include "Acts/Utilities/VectorHelpers.hpp"
0019 #include "Acts/Utilities/detail/Polynomials.hpp"
0020 
0021 #include <random>
0022 #include <ranges>
0023 
0024 using namespace Acts;
0025 using namespace Acts::UnitLiterals;
0026 using namespace Acts::VectorHelpers;
0027 using namespace Acts::Experimental::detail;
0028 using namespace Acts::Experimental;
0029 using namespace Acts::detail::LineHelper;
0030 using namespace Acts::PlanarHelper;
0031 
0032 using RandomEngine = std::mt19937;
0033 
0034 using ResidualIdx = CompSpacePointAuxiliaries::ResidualIdx;
0035 using Line_t = CompSpacePointAuxiliaries::Line_t;
0036 using normal_t = std::normal_distribution<double>;
0037 using uniform = std::uniform_real_distribution<double>;
0038 using FitParIndex = CompSpacePointAuxiliaries::FitParIndex;
0039 using ParamVec_t = CompositeSpacePointLineFitter::ParamVec_t;
0040 
0041 namespace ActsTests {
0042 
0043 class FitTestSpacePoint {
0044  public:
0045   /// @brief Constructor for standard straw wires
0046   /// @param pos: Position of the wire
0047   /// @param driftR: Straw drift radius
0048   /// @param driftRUncert: Uncertainty on the drift radius uncertainty
0049   /// @param twinUncert: Uncertainty on the measurement along the straw
0050   FitTestSpacePoint(const Vector3& pos, const double driftR,
0051                     const double driftRUncert,
0052                     const std::optional<double> twinUncert = std::nullopt)
0053       : m_position{pos},
0054         m_driftR{driftR},
0055         m_measLoc0{twinUncert != std::nullopt} {
0056     using enum ResidualIdx;
0057     m_covariance[toUnderlying(bending)] = Acts::square(driftRUncert);
0058     m_covariance[toUnderlying(nonBending)] =
0059         Acts::square(twinUncert.value_or(0.));
0060   }
0061   /// @brief Constructor for rotated straw wires
0062   /// @param pos: Position of the wire
0063   /// @param wire: Orientation of the wire
0064   /// @param driftR: Drift radius of the measurement
0065   /// @param driftRUncert: Associated uncertainty on the measurement
0066   /// @param twinUncert: Uncertainty on the measurement along the straw
0067   FitTestSpacePoint(const Vector3& pos, const Vector3& wire,
0068                     const double driftR, const double driftRUncert,
0069                     const std::optional<double> twinUncert = std::nullopt)
0070       : m_position{pos},
0071         m_sensorDir{wire},
0072         m_driftR{driftR},
0073         m_measLoc0{twinUncert != std::nullopt} {
0074     using enum ResidualIdx;
0075     m_covariance[toUnderlying(bending)] = Acts::square(driftRUncert);
0076     m_covariance[toUnderlying(nonBending)] =
0077         Acts::square(twinUncert.value_or(0.));
0078   }
0079 
0080   /// @brief Constructor for spatial strip measurements
0081   /// @param stripPos: Position of the strip
0082   /// @param stripDir: Direction along the strip
0083   /// @param toNext: Vector pointing to the next strip inside the plane
0084   /// @param uncertLoc0: Uncertainty of the measurement in the non bending direction
0085   /// @param uncertLoc1: Uncertainty of the measurement in the bending direction
0086   FitTestSpacePoint(const Vector3& stripPos, const Vector3& stripDir,
0087                     const Vector3& toNext, const double uncertLoc0,
0088                     const double uncertLoc1)
0089       : m_position{stripPos},
0090         m_sensorDir{stripDir},
0091         m_toNextSen{toNext},
0092         m_measLoc0{uncertLoc0 > 0.},
0093         m_measLoc1{uncertLoc1 > 0.} {
0094     using enum ResidualIdx;
0095     m_covariance[toUnderlying(nonBending)] = Acts::square(uncertLoc0);
0096     m_covariance[toUnderlying(bending)] = Acts::square(uncertLoc1);
0097   }
0098   /// @brief Constructor for strip measurements with time
0099   FitTestSpacePoint(const Vector3& stripPos, const Vector3& stripDir,
0100                     const Vector3& toNext, const double time,
0101                     const std::array<double, 3>& cov)
0102       : m_position{stripPos},
0103         m_sensorDir{stripDir},
0104         m_toNextSen{toNext},
0105         m_time{time},
0106         m_covariance{cov} {
0107     m_measLoc0 = m_covariance[toUnderlying(ResidualIdx::nonBending)] > 0.;
0108     m_measLoc1 = m_covariance[toUnderlying(ResidualIdx::bending)] > 0.;
0109   }
0110 
0111   /// @brief Position of the space point
0112   const Vector3& localPosition() const { return m_position; }
0113   /// @brief Wire direction
0114   const Vector3& sensorDirection() const { return m_sensorDir; }
0115   /// @brief To next sensor in the plane
0116   const Vector3& toNextSensor() const { return m_toNextSen; }
0117   /// @brief To next straw layer
0118   const Vector3& planeNormal() const { return m_planeNorm; }
0119   /// @brief Measurement's radius
0120   double driftRadius() const { return m_driftR.value_or(0.); }
0121   /// @brief Measurement's covariance
0122   const std::array<double, 3>& covariance() const { return m_covariance; }
0123   /// @brief Time of record
0124   double time() const { return m_time.value_or(0.); }
0125   /// @brief All measurements are straws
0126   bool isStraw() const { return m_driftR.has_value(); }
0127   /// @brief Check whether the space point has a time value
0128   bool hasTime() const { return m_time.has_value(); }
0129   /// @brief Check whether the space point measures the non-bending direction
0130   bool measuresLoc0() const { return m_measLoc0; }
0131   /// @brief Check whether the space point measures the bending direction
0132   bool measuresLoc1() const { return m_measLoc1 || isStraw(); }
0133   /// @brief Sets the straw tube's drift radius
0134   void updateDriftR(const double updatedR) { m_driftR = updatedR; }
0135   /// @brief Updates the position of the space point
0136   void updatePosition(const Vector3& newPos) { m_position = newPos; }
0137   /// @brief Updates the time of the space point
0138   void updateTime(const double newTime) { m_time = newTime; }
0139   /// @brief Updates the time of the space point
0140   void updateStatus(const bool newStatus) { m_isGood = newStatus; }
0141   /// @brief Check if the measurement is valid after calibration
0142   bool isGood() const { return m_isGood; }
0143   /// @brief Returns the layer index of the space point
0144   std::size_t layer() const { return m_layer.value_or(0ul); }
0145   /// @brief Sets the layer number of the space point
0146   void setLayer(const std::size_t lay) {
0147     if (!m_layer) {
0148       m_layer = lay;
0149     }
0150   }
0151 
0152  private:
0153   Vector3 m_position{Vector3::Zero()};
0154   Vector3 m_sensorDir{Vector3::UnitX()};
0155   Vector3 m_toNextSen{Vector3::UnitY()};
0156   Vector3 m_planeNorm{m_sensorDir.cross(m_toNextSen).normalized()};
0157   std::optional<double> m_driftR{std::nullopt};
0158   std::optional<double> m_time{std::nullopt};
0159   std::array<double, 3> m_covariance{filledArray<double, 3>(0)};
0160   bool m_measLoc0{false};
0161   bool m_measLoc1{false};
0162   bool m_isGood{true};
0163   std::optional<std::size_t> m_layer{};  // layer index starting from 0
0164 };
0165 
0166 static_assert(CompositeSpacePoint<FitTestSpacePoint>);
0167 
0168 using Container_t = std::vector<std::shared_ptr<FitTestSpacePoint>>;
0169 static_assert(CompositeSpacePointContainer<Container_t>);
0170 
0171 class SpCalibrator {
0172  public:
0173   SpCalibrator() = default;
0174 
0175   explicit SpCalibrator(const Acts::Transform3& localToGlobal)
0176       : m_localToGlobal{localToGlobal} {}
0177 
0178   /// @brief Normalize input variable within a given range
0179   static double normalize(const double value, const double min,
0180                           const double max) {
0181     return 2. * (std::clamp(value, min, max) - min) / (max - min) - 1.;
0182   }
0183   /// @brief Drift time validity limits for the parametrized r(t) relation
0184   static constexpr double s_minDriftTime = 0._ns;
0185   static constexpr double s_maxDriftTime = 741.324 * 1._ns;
0186   /// @brief Normalize input drift time for r(t) relation
0187   static double normDriftTime(const double t) {
0188     return normalize(t, s_minDriftTime, s_maxDriftTime);
0189   }
0190   /// @brief Multiplicative factor arising from the derivative of the normalization
0191   static constexpr double s_timeNormFactor =
0192       2.0 / (s_maxDriftTime - s_minDriftTime);
0193   /// @brief Coefficients for the parametrized r(t) relation
0194   static constexpr std::array<double, 9> s_TtoRcoeffs = {
0195       9.0102,   6.7419,   -1.5829,   0.56475, -0.19245,
0196       0.019055, 0.030006, -0.034591, 0.023517};
0197   /// @brief Parametrized ATLAS r(t) relation
0198   static double driftRadius(const double t) {
0199     const double x = normDriftTime(t);
0200     double r{0.0};
0201     for (std::size_t n = 0; n < s_TtoRcoeffs.size(); ++n) {
0202       r += s_TtoRcoeffs[n] * Acts::detail::chebychevPolyTn(x, n);
0203     }
0204     return r;
0205   }
0206   /// @brief First derivative of r(t) relation
0207   static double driftVelocity(const double t) {
0208     const double x = normDriftTime(t);
0209     double v{0.0};
0210     for (std::size_t n = 1; n < s_TtoRcoeffs.size(); ++n) {
0211       v += s_TtoRcoeffs[n] * Acts::detail::chebychevPolyUn(x, n, 1u);
0212     }
0213     return v * s_timeNormFactor;
0214   }
0215   /// @brief Second derivative of r(t) relation
0216   static double driftAcceleration(const double t) {
0217     const double x = normDriftTime(t);
0218     double a{0.0};
0219     for (std::size_t n = 2; n < s_TtoRcoeffs.size(); ++n) {
0220       a += s_TtoRcoeffs[n] * Acts::detail::chebychevPolyUn(x, n, 2u);
0221     }
0222     return a * Acts::square(s_timeNormFactor);
0223   }
0224   /// @brief Drift radius validity limits for the parametrized t(r) relation
0225   static constexpr double s_minDriftRadius = 0.064502;
0226   static constexpr double s_maxDriftRadius = 14.566;
0227   /// @brief Normalize input drift radius for t(r) relation
0228   static double normDriftRadius(const double r) {
0229     return normalize(r, s_minDriftRadius, s_maxDriftRadius);
0230   }
0231   /// @brief Coefficients for the parametrized t(r) relation
0232   static constexpr std::array<double, 5> s_RtoTcoeffs = {
0233       256.476, 349.056, 118.349, 18.748, -6.4142};
0234   /// @brief Parametrized t(r) relation
0235   static double driftTime(const double r) {
0236     const double x = normDriftRadius(r);
0237     double t{0.0};
0238     for (std::size_t n = 0; n < s_RtoTcoeffs.size(); ++n) {
0239       t += s_RtoTcoeffs[n] * Acts::detail::legendrePoly(x, n);
0240     }
0241     return t * 1._ns;
0242   }
0243   /// @brief Coefficients for the uncertanty on the drift radius
0244   static constexpr std::array<double, 4> s_driftRUncertCoeffs{
0245       0.10826, -0.07182, 0.037597, -0.011712};
0246   /// @brief Compute the drift radius uncertanty
0247   static double driftUncert(const double r) {
0248     const double x = normDriftRadius(r);
0249     double s{0.0};
0250     for (std::size_t n = 0; n < s_driftRUncertCoeffs.size(); ++n) {
0251       s += s_driftRUncertCoeffs[n] * Acts::detail::chebychevPolyTn(x, n);
0252     }
0253     return s;
0254   }
0255   /// @brief Provide the calibrated drift radius given the straw measurement and time offset
0256   ///        Needed for the Fast Fitter.
0257   /// @param ctx: Calibration context (Needed by concept interface)
0258   /// @param measurement: measurement. It should be a straw measurement
0259   /// @param timeOffSet: Offset in the time of arrival
0260   static double driftRadius(const Acts::CalibrationContext& /*ctx*/,
0261                             const FitTestSpacePoint& measurement,
0262                             const double timeOffSet) {
0263     if (!measurement.isStraw() || !measurement.isGood()) {
0264       return 0.;
0265     }
0266     return driftRadius(Acts::abs(measurement.time() - timeOffSet));
0267   }
0268   /// @brief Provide the drift velocity given the straw measurent and time offset
0269   ///        Needed for the Fast Fitter.
0270   /// @param ctx: Calibration context (Needed by concept interface)
0271   /// @param measurement: measurement
0272   /// @param timeOffSet: Offset in the time of arrival
0273   static double driftVelocity(const Acts::CalibrationContext& /*ctx*/,
0274                               const FitTestSpacePoint& measurement,
0275                               const double timeOffSet) {
0276     if (!measurement.isStraw() || !measurement.isGood()) {
0277       return 0.;
0278     }
0279     return driftVelocity(Acts::abs(measurement.time() - timeOffSet));
0280   }
0281   /// @brief Provide the drift acceleration given the straw measurent and time offset
0282   ///        Needed for the Fast Fitter.
0283   /// @param ctx: Calibration context (Needed by concept interface)
0284   /// @param measurement: measurement
0285   /// @param timeOffSet: Offset in the time of arrival
0286   static double driftAcceleration(const Acts::CalibrationContext& /*ctx*/,
0287                                   const FitTestSpacePoint& measurement,
0288                                   const double timeOffSet) {
0289     if (!measurement.isStraw() || !measurement.isGood()) {
0290       return 0.;
0291     }
0292     return driftAcceleration(Acts::abs(measurement.time() - timeOffSet));
0293   }
0294   /// @brief Compute the distance of the point of closest approach of a straw measurement
0295   /// @param ctx: Calibration context (Needed by concept interface)
0296   /// @param trackPos: Position of the track at z=0.
0297   /// @param trackDir: Direction of the track in the local frame
0298   /// @param measurement: Measurement
0299   double closestApproachDist(const Vector3& trackPos, const Vector3& trackDir,
0300                              const FitTestSpacePoint& measurement) const {
0301     const Vector3 closePointOnLine =
0302         lineIntersect(measurement.localPosition(),
0303                       measurement.sensorDirection(), trackPos, trackDir)
0304             .position();
0305     return (m_localToGlobal * closePointOnLine).norm();
0306   }
0307   std::shared_ptr<FitTestSpacePoint> calibrate(
0308       const Acts::CalibrationContext& /*cctx*/, const Vector3& trackPos,
0309       const Vector3& trackDir, const double timeOffSet,
0310       const FitTestSpacePoint& sp) const {
0311     auto copySp = std::make_shared<FitTestSpacePoint>(sp);
0312     if (!copySp->measuresLoc0() || !copySp->measuresLoc1()) {
0313       /// Estimate the best position along the sensor
0314       auto bestPos = lineIntersect(trackPos, trackDir, copySp->localPosition(),
0315                                    copySp->sensorDirection());
0316       copySp->updatePosition(bestPos.position());
0317     }
0318     if (copySp->isStraw()) {
0319       const double driftTime{copySp->time() - timeOffSet -
0320                              closestApproachDist(trackPos, trackDir, sp) /
0321                                  PhysicalConstants::c};
0322       if (driftTime < 0) {
0323         copySp->updateStatus(false);
0324         copySp->updateDriftR(0.);
0325       } else {
0326         copySp->updateStatus(true);
0327         copySp->updateDriftR(Acts::abs(driftRadius(driftTime)));
0328       }
0329     }
0330     return copySp;
0331   }
0332   /// @brief Calibrate a set of straw measurements using the best known estimate on a straight line track
0333   /// @param ctx: Calibration context (Needed by concept interface)
0334   /// @param trackPos: Position of the track at z=0.
0335   /// @param trackDir: Direction of the track in the local frame
0336   /// @param timeOffSet: Offset in the time of arrival (To be implemented)
0337   /// @param uncalibCont: Uncalibrated composite space point container
0338   Container_t calibrate(const Acts::CalibrationContext& ctx,
0339                         const Vector3& trackPos, const Vector3& trackDir,
0340                         const double timeOffSet,
0341                         const Container_t& uncalibCont) const {
0342     Container_t calibMeas{};
0343     std::ranges::transform(
0344         uncalibCont, std::back_inserter(calibMeas), [&](const auto& calibMe) {
0345           return calibrate(ctx, trackPos, trackDir, timeOffSet, *calibMe);
0346         });
0347     return uncalibCont;
0348   }
0349   /// @brief Updates the sign of the Straw's drift radii indicating that they are on the left (-1)
0350   ///        or right side (+1) of the track line
0351   void updateSigns(const Vector3& trackPos, const Vector3& trackDir,
0352                    Container_t& measurements) const {
0353     auto signs =
0354         CompSpacePointAuxiliaries::strawSigns(trackPos, trackDir, measurements);
0355     /// The signs have the same size as the measurement container
0356     for (std::size_t s = 0; s < signs.size(); ++s) {
0357       /// Take care to not turn strips into straws by accident
0358       if (measurements[s]->isStraw()) {
0359         measurements[s]->updateDriftR(
0360             Acts::abs(measurements[s]->driftRadius()) * signs[s]);
0361       }
0362     }
0363   }
0364   /// @brief Provide the drift velocity given the straw measurent after being calibrated
0365   /// @param ctx: Calibration context (Needed by concept interface)
0366   /// @param measurement: measurement
0367   static double driftVelocity(const Acts::CalibrationContext& /*ctx*/,
0368                               const FitTestSpacePoint& measurement) {
0369     if (!measurement.isStraw() || !measurement.isGood()) {
0370       return 0.;
0371     }
0372     return driftVelocity(driftTime(Acts::abs(measurement.driftRadius())));
0373   }
0374   /// @brief Provide the drift acceleration given the straw measurent after being calibrated
0375   /// @param ctx: Calibration context (Needed by concept interface)
0376   /// @param measurement: measurement
0377   static double driftAcceleration(const Acts::CalibrationContext& /*ctx*/,
0378                                   const FitTestSpacePoint& measurement) {
0379     if (!measurement.isStraw() || !measurement.isGood()) {
0380       return 0.;
0381     }
0382     return driftAcceleration(driftTime(Acts::abs(measurement.driftRadius())));
0383   }
0384 
0385  private:
0386   const Acts::Transform3 m_localToGlobal{Acts::Transform3::Identity()};
0387 };
0388 /// Ensure that the Test space point calibrator satisfies the calibrator concept
0389 static_assert(
0390     CompositeSpacePointCalibrator<SpCalibrator, Container_t, Container_t>);
0391 static_assert(
0392     CompositeSpacePointFastCalibrator<SpCalibrator, FitTestSpacePoint>);
0393 
0394 /// @brief Split the composite space point container into straw and strip measurements
0395 class SpSorter {
0396  protected:
0397   /// @brief Protected constructor to instantiate the SpSorter.
0398   /// @param hits: List of space points to sort per layer
0399   /// @param calibrator: Space point calibrator to recalibrate the hits
0400   SpSorter(const Container_t& hits, const SpCalibrator* calibrator)
0401       : m_calibrator{calibrator} {
0402     for (const auto& spPtr : hits) {
0403       auto& pushMe{spPtr->isStraw() ? m_straws : m_strips};
0404       if (spPtr->layer() >= pushMe.size()) {
0405         pushMe.resize(spPtr->layer() + 1);
0406       }
0407       pushMe[spPtr->layer()].push_back(spPtr);
0408     }
0409   }
0410 
0411  public:
0412   /// @brief Return the sorted straw hits
0413   const std::vector<Container_t>& strawHits() const { return m_straws; }
0414   /// @brief Returns the sorted strip hits
0415   const std::vector<Container_t>& stripHits() const { return m_strips; }
0416   /// @brief Returns whether the candidate space point is a good hit or not
0417   /// @param testPoint: Hit to test
0418   bool goodCandidate(const FitTestSpacePoint& testPoint) const {
0419     return testPoint.isGood();
0420   }
0421   /// @brief Calculates the pull of the space point w.r.t. to the
0422   ///        candidate seed line. To improve the pull's precision
0423   ///        the function may call the calibrator in the backend
0424   /// @param cctx: Reference to the calibration context to pipe
0425   ///              the hook for conditions access to the caller
0426   /// @param pos: Position of the cancidate seed line
0427   /// @param dir: Direction of the candidate seed line
0428   /// @param t0: Offse in the time of arrival of the particle
0429   /// @param testSp: Reference to the straw space point to test
0430   double candidateChi2(const CalibrationContext& /* cctx*/, const Vector3& pos,
0431                        const Vector3& dir, const double /*t0*/,
0432                        const FitTestSpacePoint& testSp) const {
0433     return CompSpacePointAuxiliaries::chi2Term(pos, dir, testSp);
0434   }
0435   /// @brief Creates a new empty container
0436   Container_t newContainer(const CalibrationContext& /*cctx*/) const {
0437     return Container_t{};
0438   }
0439   /// @brief Appends the space point to the container and calibrates it according to
0440   ///        the seed parameters
0441   ///
0442   void append(const CalibrationContext& cctx, const Vector3& pos,
0443               const Vector3& dir, const double t0,
0444               const FitTestSpacePoint& testSp,
0445               Container_t& outContainer) const {
0446     outContainer.push_back(m_calibrator->calibrate(cctx, pos, dir, t0, testSp));
0447   }
0448 
0449   bool stopSeeding(const std::size_t lowerLayer,
0450                    const std::size_t upperLayer) const {
0451     return lowerLayer >= upperLayer;
0452   }
0453   double strawRadius(const FitTestSpacePoint& /*testSp*/) const {
0454     return 15._mm;
0455   }
0456 
0457  private:
0458   const SpCalibrator* m_calibrator{nullptr};
0459   std::vector<Container_t> m_straws{};
0460   std::vector<Container_t> m_strips{};
0461 };
0462 
0463 static_assert(CompositeSpacePointSorter<SpSorter, Container_t>);
0464 
0465 /// @brief Generates a random straight line
0466 /// @param engine Random number sequence to draw the parameters from
0467 /// @return A Line object instantiated with the generated parameters
0468 Line_t generateLine(RandomEngine& engine, const Logger& logger) {
0469   using ParIndex = Line_t::ParIndex;
0470   Line_t::ParamVector linePars{};
0471   linePars[toUnderlying(ParIndex::phi)] =
0472       uniform{-120_degree, 120_degree}(engine);
0473   linePars[toUnderlying(ParIndex::x0)] = uniform{-500., 500.}(engine);
0474   linePars[toUnderlying(ParIndex::y0)] = uniform{-500., 500.}(engine);
0475   linePars[toUnderlying(ParIndex::theta)] =
0476       uniform{5_degree, 175_degree}(engine);
0477   if (Acts::abs(linePars[toUnderlying(ParIndex::theta)] - 90._degree) <
0478       3_degree) {
0479     return generateLine(engine, logger);
0480   }
0481   Line_t line{linePars};
0482 
0483   ACTS_DEBUG(
0484       "\n\n\n"
0485       << __func__ << "() " << __LINE__ << " - Generated parameters "
0486       << std::format("theta: {:.2f}, phi: {:.2f}, y0: {:.1f}, x0: {:.1f}",
0487                      linePars[toUnderlying(ParIndex::theta)] / 1._degree,
0488                      linePars[toUnderlying(ParIndex::phi)] / 1._degree,
0489                      linePars[toUnderlying(ParIndex::y0)],
0490                      linePars[toUnderlying(ParIndex::x0)])
0491       << " --> " << toString(line.position()) << " + "
0492       << toString(line.direction()));
0493 
0494   return line;
0495 }
0496 
0497 class MeasurementGenerator {
0498  public:
0499   struct Config {
0500     /// @brief Create straw measurements
0501     bool createStraws{true};
0502     /// @brief Smear the straw radius
0503     bool smearRadius{true};
0504     /// @brief Straw measurement measures the coordinate along the wire
0505     bool twinStraw{false};
0506     /// @brief Resolution of the coordinate along the wire measurement
0507     double twinStrawReso{5._cm};
0508     /// @brief Create strip measurements
0509     bool createStrips{true};
0510     /// @brief Smear the strips around the pitch
0511     bool smearStrips{true};
0512     /// @brief Alternatively, discretize the strips onto a strip plane
0513     bool discretizeStrips{false};
0514     /// @brief Combine the two strip measurements to a single space point
0515     bool combineSpacePoints{false};
0516     /// @brief Pitch between two loc0 strips
0517     double stripPitchLoc0{4._cm};
0518     /// @brief Pitch between two loc1 strips
0519     double stripPitchLoc1{3._cm};
0520     /// @brief Direction of the strip if it measures loc1
0521     std::vector<Vector3> stripDirLoc1 =
0522         std::vector<Vector3>(8, Vector3::UnitX());
0523     /// @brief Direction of the strip if it measures loc0
0524     std::vector<Vector3> stripDirLoc0 =
0525         std::vector<Vector3>(8, Vector3::UnitY());
0526     /// @brief Experiment specific calibration context
0527     Acts::CalibrationContext calibContext{};
0528     /// @brief Local to global transform
0529     Acts::Transform3 localToGlobal{Acts::Transform3::Identity()};
0530   };
0531   /// @brief Extrapolate the straight line track through the straw layers to
0532   ///        evaluate which tubes were crossed by the track. The straw layers
0533   ///        are staggered in the z-direction and each layer expands in y. To
0534   ///        estimate, which tubes were crossed, the track is extrapolated to
0535   ///        the z-plane below & above the straw wires. Optionally, the true
0536   ///        drift-radius can be smeared assuming a Gaussian with a drift-radius
0537   ///        dependent uncertainty.
0538   /// @param line: The track to extrapolate
0539   /// @param engine: Random number generator to smear the drift radius
0540   /// @param smearRadius: If true, the drift radius is smeared with a Gaussian
0541   /// @param createStrips: If true the strip measurements are created
0542   static Container_t spawn(const Line_t& line, const double t0,
0543                            RandomEngine& engine, const Config& genCfg,
0544                            const Logger& logger) {
0545     /// Direction vector to go a positive step in the tube honeycomb grid
0546     const Vector3 posStaggering{0., std::cos(60._degree), std::sin(60._degree)};
0547     /// Direction vector to go a negative step in the tube honeycomb grid
0548     const Vector3 negStaggering{0., -std::cos(60._degree),
0549                                 std::sin(60._degree)};
0550     /// Number of tube layers per multilayer
0551     constexpr std::size_t nLayersPerMl = 8;
0552     /// Number of overall tubelayers
0553     constexpr std::size_t nTubeLayers = nLayersPerMl * 2;
0554     /// Position in z of the first tube layer
0555     constexpr double chamberDistance = 3._m;
0556     /// Radius of each straw
0557     constexpr double tubeRadius = 15._mm;
0558     /// Distance between the first <nLayersPerMl> layers and the second pack
0559     constexpr double tubeLayerDist = 1.2_m;
0560     /// Distance between the tube multilayers and to the first strip layer
0561     constexpr double tubeStripDist = 30._cm;
0562     /// Distance between two strip layers
0563     constexpr double stripLayDist = 0.5_cm;
0564 
0565     std::array<Vector3, nTubeLayers> tubePositions{
0566         filledArray<Vector3, nTubeLayers>(chamberDistance * Vector3::UnitZ())};
0567     /// Fill the positions of the reference tubes 1
0568     for (std::size_t l = 1; l < nTubeLayers; ++l) {
0569       const Vector3& layStag{l % 2 == 1 ? posStaggering : negStaggering};
0570       tubePositions[l] = tubePositions[l - 1] + 2. * tubeRadius * layStag;
0571 
0572       if (l == nLayersPerMl) {
0573         tubePositions[l] += tubeLayerDist * Vector3::UnitZ();
0574       }
0575     }
0576     /// Print the staggering
0577     ACTS_DEBUG("##############################################");
0578 
0579     for (std::size_t l = 0; l < nTubeLayers; ++l) {
0580       ACTS_DEBUG("  *** " << (l + 1) << " - " << toString(tubePositions[l]));
0581     }
0582     ACTS_DEBUG("##############################################");
0583 
0584     Container_t measurements{};
0585     /// Extrapolate the track to the z-planes of the tubes and determine which
0586     /// tubes were actually hit
0587     if (genCfg.createStraws) {
0588       const SpCalibrator calibrator{genCfg.localToGlobal};
0589       for (const auto& [i_layer, stag] : enumerate(tubePositions)) {
0590         auto planeExtpLow =
0591             intersectPlane(line.position(), line.direction(), Vector3::UnitZ(),
0592                            stag.z() - tubeRadius);
0593         auto planeExtpHigh =
0594             intersectPlane(line.position(), line.direction(), Vector3::UnitZ(),
0595                            stag.z() + tubeRadius);
0596         ACTS_DEBUG("spawn() - Extrapolated to plane "
0597                    << toString(planeExtpLow.position()) << " "
0598                    << toString(planeExtpHigh.position()));
0599 
0600         const auto dToFirstLow = static_cast<int>(std::ceil(
0601             (planeExtpLow.position().y() - stag.y()) / (2. * tubeRadius)));
0602         const auto dToFirstHigh = static_cast<int>(std::ceil(
0603             (planeExtpHigh.position().y() - stag.y()) / (2. * tubeRadius)));
0604         /// Does the track go from left to right or right to left?
0605         const int dT = copySign(1, dToFirstHigh - dToFirstLow);
0606         /// Loop over the candidate tubes and check each one whether the track
0607         /// actually crossed them. Then generate the circle and optionally smear
0608         /// the radius
0609         for (int tN = dToFirstLow - dT; tN != dToFirstHigh + 2 * dT; tN += dT) {
0610           Vector3 tube = stag + 2. * tN * tubeRadius * Vector3::UnitY();
0611           const double rad = Acts::abs(signedDistance(
0612               tube, Vector3::UnitX(), line.position(), line.direction()));
0613           if (rad > tubeRadius) {
0614             continue;
0615           }
0616 
0617           const double smearedR =
0618               genCfg.smearRadius
0619                   ? Acts::abs(
0620                         normal_t{rad, SpCalibrator::driftUncert(rad)}(engine))
0621                   : rad;
0622           if (smearedR > tubeRadius) {
0623             continue;
0624           }
0625           ///
0626           if (genCfg.twinStraw) {
0627             const auto iSectWire = lineIntersect<3>(
0628                 line.position(), line.direction(), tube, Vector3::UnitX());
0629             tube = iSectWire.position();
0630             tube[eX] = normal_t{tube[eX], genCfg.twinStrawReso}(engine);
0631           }
0632           ACTS_DEBUG("spawn() - Tube position: " << toString(tube)
0633                                                  << ", radius: " << rad);
0634 
0635           auto twinUncert =
0636               genCfg.twinStraw
0637                   ? std::make_optional<double>(genCfg.twinStrawReso)
0638                   : std::nullopt;
0639           auto& sp =
0640               measurements.emplace_back(std::make_unique<FitTestSpacePoint>(
0641                   tube, smearedR, SpCalibrator::driftUncert(smearedR),
0642                   twinUncert));
0643           sp->setLayer(i_layer);
0644           sp->updateTime(SpCalibrator::driftTime(sp->driftRadius()) + t0 +
0645                          calibrator.closestApproachDist(line.position(),
0646                                                         line.direction(), *sp) /
0647                              PhysicalConstants::c);
0648         }
0649       }
0650     }
0651     if (genCfg.createStrips) {
0652       ///@brief Helper function to discretize the extrapolated position on the strip plane to actual strips.
0653       ///       The function returns the local coordinate in the picked plane
0654       ///       projection
0655       /// @param extPos: Extrapolated position of the straight line in the plane
0656       /// @param loc1: Boolean to fetch either the loc1 projection or the loc0 projection
0657       auto discretize = [&genCfg, &engine](const Vector3& extPos,
0658                                            const std::size_t lay,
0659                                            const bool loc1) -> Vector3 {
0660         const double pitch =
0661             loc1 ? genCfg.stripPitchLoc1 : genCfg.stripPitchLoc0;
0662         assert(pitch > 0.);
0663 
0664         const Vector3& stripDir =
0665             loc1 ? genCfg.stripDirLoc1.at(lay) : genCfg.stripDirLoc0.at(lay);
0666         const Vector3 stripNormal = stripDir.cross(Vector3::UnitZ());
0667         const double dist = stripNormal.dot(extPos);
0668         if (genCfg.smearStrips) {
0669           return normal_t{dist, pitch / std::sqrt(12)}(engine)*stripNormal;
0670         }
0671         if (genCfg.discretizeStrips) {
0672           return pitch *
0673                  static_cast<int>(std::ceil((dist - 0.5 * pitch) / pitch)) *
0674                  stripNormal;
0675         }
0676         return dist * stripNormal;
0677       };
0678       /// Calculate the strip measurement's covariance
0679       const double stripCovLoc0 =
0680           Acts::square(genCfg.stripPitchLoc0) / std::sqrt(12.);
0681       const double stripCovLoc1 =
0682           Acts::square(genCfg.stripPitchLoc1) / std::sqrt(12.);
0683 
0684       for (std::size_t sL = 0; sL < std::max(genCfg.stripDirLoc0.size(),
0685                                              genCfg.stripDirLoc1.size());
0686            ++sL) {
0687         const double distInZ = tubeStripDist + sL * stripLayDist;
0688         const double planeLow = tubePositions.front().z() - distInZ;
0689         const double planeHigh = tubePositions.back().z() + distInZ;
0690 
0691         for (const double plane : {planeLow, planeHigh}) {
0692           const auto extp = intersectPlane(line.position(), line.direction(),
0693                                            Vector3::UnitZ(), plane);
0694           ACTS_VERBOSE("spawn() - Propagated line to "
0695                        << toString(extp.position()) << ".");
0696 
0697           if (genCfg.combineSpacePoints &&
0698               sL < std::min(genCfg.stripDirLoc0.size(),
0699                             genCfg.stripDirLoc1.size())) {
0700             const Vector3 extpPos{discretize(extp.position(), sL, false) +
0701                                   discretize(extp.position(), sL, true) +
0702                                   plane * Vector3::UnitZ()};
0703             measurements.emplace_back(std::make_unique<FitTestSpacePoint>(
0704                 extpPos, genCfg.stripDirLoc0.at(sL), genCfg.stripDirLoc1.at(sL),
0705                 stripCovLoc0, stripCovLoc1));
0706             measurements.back()->setLayer(sL);
0707           } else {
0708             if (sL < genCfg.stripDirLoc0.size()) {
0709               const Vector3 extpPos{discretize(extp.position(), sL, false) +
0710                                     plane * Vector3::UnitZ()};
0711               measurements.emplace_back(std::make_unique<FitTestSpacePoint>(
0712                   extpPos, genCfg.stripDirLoc0.at(sL),
0713                   genCfg.stripDirLoc0.at(sL).cross(Vector3::UnitZ()),
0714                   stripCovLoc0, 0.));
0715               auto& nM{*measurements.back()};
0716               nM.setLayer(sL);
0717               ACTS_VERBOSE("spawn() - Created loc0 strip @" << toString(nM)
0718                                                             << ".");
0719             }
0720             if (sL < genCfg.stripDirLoc1.size()) {
0721               const Vector3 extpPos{discretize(extp.position(), sL, true) +
0722                                     plane * Vector3::UnitZ()};
0723               measurements.emplace_back(std::make_unique<FitTestSpacePoint>(
0724                   extpPos, genCfg.stripDirLoc1.at(sL),
0725                   genCfg.stripDirLoc1.at(sL).cross(Vector3::UnitZ()), 0.,
0726                   stripCovLoc1));
0727               auto& nM{*measurements.back()};
0728               nM.setLayer(sL);
0729               ACTS_VERBOSE("spawn() - Created loc1 strip @" << toString(nM)
0730                                                             << ".");
0731             }
0732           }
0733         }
0734       }
0735     }
0736     ACTS_DEBUG("Track hit in total " << measurements.size() << " sensors.");
0737     std::ranges::sort(measurements, [&line](const auto& spA, const auto& spB) {
0738       return line.direction().dot(spA->localPosition() - line.position()) <
0739              line.direction().dot(spB->localPosition() - line.position());
0740     });
0741     return measurements;
0742   }
0743 };
0744 
0745 /// @brief Construct the start parameters from the hit container && the true trajectory
0746 /// @param line: True trajectory to pick-up the correct left/right ambiguity for the straw seed hits
0747 /// @param hits: List of measurements to be used for fitting
0748 ParamVec_t startParameters(const Line_t& line, const Container_t& hits) {
0749   ParamVec_t pars{};
0750 
0751   double tanAlpha{0.};
0752   double tanBeta{0.};
0753   /// Setup the seed parameters in x0 && phi
0754   auto firstPhi = std::ranges::find_if(
0755       hits, [](const auto& sp) { return sp->measuresLoc0(); });
0756   auto lastPhi =
0757       std::ranges::find_if(std::ranges::reverse_view(hits),
0758                            [](const auto& sp) { return sp->measuresLoc0(); });
0759 
0760   if (firstPhi != hits.end() && lastPhi != hits.rend()) {
0761     const Vector3 firstToLastPhi =
0762         (**lastPhi).localPosition() - (**firstPhi).localPosition();
0763     tanAlpha = firstToLastPhi.x() / firstToLastPhi.z();
0764     /// -> x = tanPhi * z + x_{0} ->
0765     pars[toUnderlying(FitParIndex::x0)] =
0766         (**lastPhi).localPosition().x() -
0767         (**lastPhi).localPosition().z() * tanAlpha;
0768   }
0769   /// Setup the seed parameters in y0 && theta
0770   auto firstTube =
0771       std::ranges::find_if(hits, [](const auto& sp) { return sp->isStraw(); });
0772   auto lastTube =
0773       std::ranges::find_if(std::ranges::reverse_view(hits),
0774                            [](const auto& sp) { return sp->isStraw(); });
0775 
0776   if (firstTube != hits.end() && lastTube != hits.rend()) {
0777     const int signFirst =
0778         CompSpacePointAuxiliaries::strawSign(line, **firstTube);
0779     const int signLast = CompSpacePointAuxiliaries::strawSign(line, **lastTube);
0780 
0781     auto seedPars = CompositeSpacePointLineSeeder::constructTangentLine(
0782         **lastTube, **firstTube,
0783         CompositeSpacePointLineSeeder::encodeAmbiguity(signLast, signFirst));
0784     tanBeta = std::tan(seedPars.theta);
0785     pars[toUnderlying(FitParIndex::y0)] = seedPars.y0;
0786   } else {
0787     auto firstEta = std::ranges::find_if(hits, [](const auto& sp) {
0788       return !sp->isStraw() && sp->measuresLoc1();
0789     });
0790     auto lastEta = std::ranges::find_if(
0791         std::ranges::reverse_view(hits),
0792         [](const auto& sp) { return !sp->isStraw() && sp->measuresLoc1(); });
0793 
0794     if (firstEta != hits.end() && lastEta != hits.rend()) {
0795       const Vector3 firstToLastEta =
0796           (**lastEta).localPosition() - (**firstEta).localPosition();
0797       tanBeta = firstToLastEta.y() / firstToLastEta.z();
0798       /// -> y = tanTheta * z + y_{0} ->
0799       pars[toUnderlying(FitParIndex::y0)] =
0800           (**lastEta).localPosition().y() -
0801           (**lastEta).localPosition().z() * tanBeta;
0802     }
0803   }
0804   const Vector3 seedDir = makeDirectionFromAxisTangents(tanAlpha, tanBeta);
0805 
0806   pars[toUnderlying(FitParIndex::theta)] = theta(seedDir);
0807   pars[toUnderlying(FitParIndex::phi)] = phi(seedDir);
0808   return pars;
0809 }
0810 
0811 bool isGoodHit(const FitTestSpacePoint& sp) {
0812   return !sp.isStraw() || sp.isGood();
0813 };
0814 
0815 }  // namespace ActsTests