Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-12-14 09:23:07

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