diff --git a/.clang-format b/.clang-format index 93ba0f7b0c187..f0eb7d31df8e3 100644 --- a/.clang-format +++ b/.clang-format @@ -54,3 +54,7 @@ UseTab: Never # Do not format protobuf files Language: Proto DisableFormat: true +--- +# Do not format JSON configuration files +Language: Json +DisableFormat: true diff --git a/.github/workflows/code-transformations.yml b/.github/workflows/code-transformations.yml deleted file mode 100644 index 35493afda94f5..0000000000000 --- a/.github/workflows/code-transformations.yml +++ /dev/null @@ -1,84 +0,0 @@ -name: Current refactorings - -on: [pull_request_target] -env: - RATIONALE: "Adapt to new FairLogger API" - REFACTORING: "s|LOGP[(]ERROR|LOGP(error|g;s|LOGP[(]INFO|LOGP(info|g;s|LOGP[(]WARNING|LOGP(warning|g;s|LOGP[(]WARN|LOGP(warn|g;s|LOGP[(]DEBUG|LOGP(debug|;s|LOG[(]ERROR|LOG(error|g;s|LOG[(]INFO|LOG(info|g;s|LOG[(]WARNING|LOG(warning|g;s|LOG[(]WARN|LOG(warn|g;s|LOG[(]DEBUG|LOG(debug|g" - -jobs: - build: - # We need at least 20.04 to install clang-format-11. - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v5 - with: - ref: ${{ github.event.pull_request.head.sha }} - persist-credentials: false - # We need the history of the dev branch all the way back to where the PR - # diverged. We're fetching everything here, as we don't know how many - # commits back that point is. - fetch-depth: 0 - - - name: Run refactoring - id: run_refactoring - env: - ALIBUILD_GITHUB_TOKEN: ${{secrets.ALIBUILD_GITHUB_TOKEN}} - run: | - set -x - # We need to fetch the other commit. - git fetch origin ${{ github.event.pull_request.base.ref }} \ - pull/${{ github.event.pull_request.number }}/head:${{ github.event.pull_request.head.ref }} - - # We create a new branch which we will use for the eventual PR. - git config --global user.email "alibuild@cern.ch" - git config --global user.name "ALICE Action Bot" - git checkout -b alibot-refactor-${{ github.event.pull_request.number }} ${{ github.event.pull_request.head.sha }} - - # github.event.pull_request.base.sha is the latest commit on the branch - # the PR will be merged into, NOT the commit this PR derives from! For - # that, we need to find the latest common ancestor between the PR and - # the branch we are merging into. - BASE_COMMIT=$(git merge-base HEAD ${{ github.event.pull_request.base.sha }}) - echo "Running refactoring against branch ${{ github.event.pull_request.base.ref }}, with hash ${{ github.event.pull_request.base.sha }}" - COMMIT_FILES=$(git diff --diff-filter d --name-only $BASE_COMMIT) - if [ -z "$COMMIT_FILES" ]; then - echo "No files to check" >&2 - echo clean=true >> "$GITHUB_OUTPUT" - exit 0 - fi - perl -p -i -e "${{ env.REFACTORING }}" $COMMIT_FILES - - if git diff --exit-code; then - echo "Refactoring not needed." - git push --set-upstream https://alibuild:$ALIBUILD_GITHUB_TOKEN@github.com/alibuild/AliceO2.git :alibot-refactoring-${{ github.event.pull_request.number }} -f || true - echo clean=true >> "$GITHUB_OUTPUT" - else - git commit -m "${{ env.RATIONALE }}" -a - git show | cat - git fetch https://github.com/AliceO2Group/AliceO2.git pull/${{ github.event.pull_request.number }}/head - git push --set-upstream https://alibuild:$ALIBUILD_GITHUB_TOKEN@github.com/alibuild/AliceO2.git HEAD:refs/heads/alibot-refactoring-${{ github.event.pull_request.number }} -f - echo clean=false >> "$GITHUB_OUTPUT" - fi - - - name: pull-request - uses: alisw/pull-request@master - with: - source_branch: 'alibuild:alibot-refactoring-${{ github.event.pull_request.number }}' - destination_branch: '${{ github.event.pull_request.head.label }}' - github_token: ${{ secrets.ALIBUILD_GITHUB_TOKEN }} - pr_title: "Please consider the refactoring changes to AliceO2Group/AliceO2#${{ github.event.pull_request.number }}" - pr_body: | - AliceO2Group/AliceO2#${{ github.event.pull_request.number }}" cannot be merged as is. - You should either modify your code according to what is done in this PR, or directly merge this PR in yours. - The rationale for this change is: - ${{ env.RATIONALE }} - - continue-on-error: true # We do not create PRs if the branch is not there. - - - name: Exit with error if the PR is not clean - run: | - case ${{ steps.run_refactoring.outputs.clean }} in - true) echo "PR clean" ; exit 0 ;; - false) echo "PR not clean" ; exit 1 ;; - esac diff --git a/.github/workflows/datamodel-doc.yml b/.github/workflows/datamodel-doc.yml index 3ba015631aec6..1dd54790bff76 100644 --- a/.github/workflows/datamodel-doc.yml +++ b/.github/workflows/datamodel-doc.yml @@ -40,7 +40,7 @@ jobs: git checkout -B auto-datamodel-doc - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: 3.x diff --git a/.github/workflows/reports.yml b/.github/workflows/reports.yml index 5a04e56382fb3..444ca1002f6ff 100644 --- a/.github/workflows/reports.yml +++ b/.github/workflows/reports.yml @@ -19,7 +19,7 @@ jobs: steps: - uses: actions/checkout@v5 - name: Set up Python 3.10 - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: '3.10' - uses: actions/cache@v5 diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 23f454aaca950..44e9072aabf6f 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -7,7 +7,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v10 + - uses: actions/stale@v11 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-pr-message: 'This PR did not have any update in the last 30 days. Is it still needed? Unless further action in will be closed in 5 days.' diff --git a/CCDB/include/CCDB/CcdbApi.h b/CCDB/include/CCDB/CcdbApi.h index 4dab11d5972d8..acb0ad10f6c9a 100644 --- a/CCDB/include/CCDB/CcdbApi.h +++ b/CCDB/include/CCDB/CcdbApi.h @@ -39,7 +39,7 @@ class TJAlienCredentials; #include "CCDB/CCDBDownloader.h" class TFile; -class TGrid; +#include namespace o2 { diff --git a/Common/DCAFitter/DCAFitterN_derivation.md b/Common/DCAFitter/DCAFitterN_derivation.md new file mode 100644 index 0000000000000..eb1c7e36c1fe2 --- /dev/null +++ b/Common/DCAFitter/DCAFitterN_derivation.md @@ -0,0 +1,552 @@ +# DCAFitterN derivation notes + +This file combines the extracted content of +`~/Downloads/DCAFitterN_derivation_for_codex.md` with the O2-specific covariance +updates made in `include/DCAFitter/DCAFitterN.h`. + +## 1. Convention check from the extracted scan + +The extracted scan starts with a matrix named `M_i` and states that it maps local +track coordinates to global coordinates: + +```text +p_i^g = M_i p_i +``` + +but the printed 2D block + +```text +[ cos(alpha_i) sin(alpha_i) ] +[ -sin(alpha_i) cos(alpha_i) ] +``` + +is the inverse of the O2 local-to-global rotation. O2 uses + +```text +R_i = +[ cos(alpha_i) -sin(alpha_i) 0 ] +[ sin(alpha_i) cos(alpha_i) 0 ] +[ 0 0 1 ] +``` + +with + +```text +p_i^g = R_i p_i, p_i = R_i^T p_i^g . +``` + +The later extracted formulas note this possible sign reversal. The code in +`DCAFitterN.h` matches the O2 convention above. In the rest of this document +`R_i` is always the O2 local-to-global rotation. + +## 2. Weighted N-prong vertex fit + +For track `i`, let the current point in its local frame be + +```text +p_i = (x_i, y_i, z_i)^T . +``` + +Let `B_i` be the inverse covariance, or information matrix, for this local +point. The fitted vertex `V` is in the global frame. Its representation in +track `i`'s local frame is `R_i^T V`, so the residual is + +```text +Delta_i = p_i - R_i^T V . +``` + +The weighted objective is + +```text +chi2 = 1/2 sum_i Delta_i^T B_i Delta_i . +``` + +For fixed track points, differentiating with respect to `V` gives + +```text +A V = sum_i R_i B_i p_i +``` + +where + +```text +A = sum_i R_i B_i R_i^T . +``` + +Therefore + +```text +V = A^{-1} sum_i R_i B_i p_i . +``` + +Define + +```text +T_i = A^{-1} R_i B_i . +``` + +Then + +```text +V = sum_i T_i p_i . +``` + +This is the `calcInverseWeight`, `calcPCACoefs`, and `calcPCA` structure in the +code. The useful identity is + +```text +sum_i T_i R_i^T = I . +``` + +## 3. Residuals after eliminating the vertex + +Substituting `V = sum_k T_k p_k` into the residual gives + +```text +Delta_i = sum_k D_ik p_k +``` + +with + +```text +D_ik = delta_ik I - R_i^T T_k . +``` + +The fit parameters are the local running coordinates `x_k`. During one Newton +linearization, `R_i`, `B_i`, `A`, and `T_i` are treated as fixed. Each track +point depends only on its own parameter: + +```text +d p_i / d x_k = delta_ik p_i' +``` + +so + +```text +d Delta_i / d x_k = D_ik p_k' +``` + +and + +```text +d^2 Delta_i / d x_k d x_l = delta_kl D_ik p_k'' . +``` + +## 4. Track derivatives in the O2 local frame + +O2 central-barrel parameters are + +```text +(Y, Z, snp, tgl, q/pt) +``` + +where + +```text +snp = sin(phi_local), csp = sqrt(1 - snp^2), +tgl = tan(lambda), kappa = curvature = (q/pt) Bz B2C . +``` + +For the fast constant-`Bz` helix model: + +```text +d snp / dX = kappa +dY / dX = snp / csp +dZ / dX = tgl / csp +``` + +and + +```text +d2Y / dX2 = kappa / csp^3 +d2Z / dX2 = kappa tgl snp / csp^3 . +``` + +Thus + +```text +p_i' = (1, dY/dX, dZ/dX)^T +p_i'' = (0, d2Y/dX2, d2Z/dX2)^T . +``` + +This matches `TrackDeriv::set`. + +## 5. Gradient and Hessian + +With symmetric `B_i`, + +```text +g_k = d chi2 / d x_k + = sum_i Delta_i^T B_i D_ik p_k' . +``` + +The exact Hessian is + +```text +H_kl = + sum_i (D_il p_l')^T B_i (D_ik p_k') + + delta_kl sum_i Delta_i^T B_i D_ik p_k'' . +``` + +The first term is the Gauss-Newton term. It contributes to diagonal and mixed +Hessian elements. The second term is the residual-curvature term. Since each +trajectory has an intrinsic second derivative only with respect to its own +running coordinate, this term contributes only when `k == l`. This is the +reason for the code condition + +```cpp +if (i == j) { + ... +} +``` + +when computing the Hessian element `H_ij`. + +The implementation solves + +```text +H dX = g +``` + +and then applies + +```text +X_new = X_old - dX . +``` + +This is equivalent to the more usual Newton notation `deltaX = -H^{-1} g`. + +## 6. No-error special case + +For the absolute-distance fit, take all information matrices as identity: + +```text +B_i = I . +``` + +Then + +```text +A = N I, A^{-1} = (1/N) I, +T_i = (1/N) R_i . +``` + +The vertex is the average of global track points: + +```text +V = (1/N) sum_i R_i p_i . +``` + +The residual is + +```text +Delta_i = p_i - (1/N) sum_j R_i^T R_j p_j . +``` + +Define + +```text +R_ij = (1/N) R_i^T R_j . +``` + +With the O2 convention, + +```text +R_i^T R_j = +[ cos(ai-aj) sin(ai-aj) 0 ] +[ -sin(ai-aj) cos(ai-aj) 0 ] +[ 0 0 1 ] . +``` + +This matches the code definitions + +```cpp +mCosDif[i][j] = (ci*cj + si*sj) / N; +mSinDif[i][j] = (si*cj - ci*sj) / N; +``` + +and the residual derivative components in `calcResidDerivativesNoErr`. + +## 7. Track information matrix involving the local X axis + +The old DCAFitterN code assigned a dummy uncertainty to local `X`, derived from +the local `Y` uncertainty. The corrected treatment derives longitudinal vertex +information from the track geometry. + +At fixed local `X`, the track measures `(Y,Z)` with covariance + +```text +C_YZ = +[ C_YY C_YZ ] +[ C_YZ C_ZZ ] . +``` + +Let + +```text +D = C_YY C_ZZ - C_YZ^2 +``` + +and + +```text +W = C_YZ^{-1} + = 1/D [ C_ZZ -C_YZ ] + [ -C_YZ C_YY ] . +``` + +Writing + +```text +wYY = C_ZZ / D +wYZ = -C_YZ / D +wZZ = C_YY / D +``` + +and + +```text +y' = dY/dX = snp/csp +z' = dZ/dX = tgl/csp +``` + +the vertex measurement matrix in local `(X,Y,Z)` coordinates is + +```text +H = +[ -y' 1 0 ] +[ -z' 0 1 ] . +``` + +The local 3D information matrix is + +```text +I = H^T W H . +``` + +Its independent elements are + +```text +I_YY = wYY +I_YZ = wYZ +I_ZZ = wZZ +I_XY = -(wYY y' + wYZ z') +I_XZ = -(wYZ y' + wZZ z') +I_XX = y'^2 wYY + 2 y' z' wYZ + z'^2 wZZ . +``` + +These are the six members of `TrackCovI`: + +```text +sxx, sxy, sxz, syy, syz, szz . +``` + +To combine tracks in the global frame, rotate the local information: + +```text +I_i^g = R_i I_i R_i^T . +``` + +The vertex covariance is then + +```text +C_V = (sum_i I_i^g)^{-1} . +``` + +## 8. Parent momentum covariance + +The parent momentum is the sum of independent daughter momenta: + +```text +P = sum_i p_i, C_P = sum_i C_{p_i} . +``` + +For one O2 daughter track, + +```text +px = pt (csp cos(alpha) - snp sin(alpha)) +py = pt (snp cos(alpha) + csp sin(alpha)) +pz = pt tgl +``` + +with `pt = |q|/|q/pt|`. The charge is treated as exact and discrete. The +derivatives with respect to native momentum parameters `(snp, tgl, q/pt)` are + +```text +dpx/dsnp = -pt (snp cos(alpha)/csp + sin(alpha)) +dpy/dsnp = pt (cos(alpha) - snp sin(alpha)/csp) +dpz/dtgl = pt + +dpx/d(q/pt) = -px / (q/pt) +dpy/d(q/pt) = -py / (q/pt) +dpz/d(q/pt) = -pz / (q/pt) +``` + +and the omitted mixed derivatives in this Jacobian are zero: + +```text +dpx/dtgl = 0, dpy/dtgl = 0, +dpz/dsnp = 0 . +``` + +Let `A` be this 3 by 3 Jacobian and let `C_a` be the daughter covariance +submatrix in the native momentum-parameter order `(snp,tgl,q/pt)`. Then + +```text +C_p = A C_a A^T . +``` + +The six independent elements of `C_p` are accumulated into the O2 lab covariance +slots + +```text +cov[9], cov[13], cov[14], cov[18], cov[19], cov[20] +``` + +which correspond to + +```text +Cov(px,px), Cov(py,px), Cov(py,py), +Cov(pz,px), Cov(pz,py), Cov(pz,pz). +``` + +## 9. Parent track covariance + +The final lab covariance passed to the O2 parent constructor contains the fitted +vertex covariance and the summed parent momentum covariance: + +```text +C_lab = +[ C_V 0 ] +[ 0 C_P ] . +``` + +Position-momentum cross-covariances are not included in this approximation. +The existing O2 constructor + +```cpp +TrackParCov(xyz, pxpypz, cov, charge, sectorAlpha) +``` + +then transforms this lab covariance to the selected parent track frame, +including the parent `alpha` convention. + +## 10. Regularization of singular or weakly constrained covariance matrices + +### Single-track `TrackCovI` + +The matrix + +```text +I = H^T W H +``` + +has rank at most two for one track, because one track measures only the two +coordinates transverse to its own trajectory. The null direction is the local +track tangent + +```text +t = (1, y_prime, z_prime)^T . +``` + +Indeed + +```text +H t = 0, I t = 0 . +``` + +Therefore a single-track `TrackCovI` is positive semidefinite, not positive +definite. Inverting this 3D matrix by itself is not meaningful; the apparent +negative diagonal elements seen in such an inverse are numerical symptoms of +trying to invert a rank-deficient information matrix. The physically meaningful +inverse at single-track level is only the original 2 by 2 `(Y,Z)` covariance. + +For the multi-track vertex fit, different track directions usually make + +```text +A = sum_i R_i I_i R_i^T +``` + +full rank. However, nearly parallel or otherwise weak geometries can still make +the longitudinal eigenvalue very small. To avoid an exactly singular +single-track contribution while preserving the measured `(Y,Z)` block, the code +may add a weak positive longitudinal prior only to `I_XX`: + +```text +I_XX -> I_XX + 1/sigma_X,prior^2 . +``` + +In code this is implemented as + +```cpp +constexpr float XRegErrFactor = 10.f; +const float sigmaX2 = C_YY * XRegErrFactor; +sxx += 1.f / sigmaX2; +``` + +This is different from multiplying `I_XX` by a number below one. A reduction of +`I_XX` can make the matrix indefinite, while adding a positive diagonal term +keeps the information matrix positive definite in the tangent direction and does +not alter `I_YY`, `I_YZ`, or `I_ZZ`. + +The regularization should remain weak. It is a numerical stabilizer for badly +conditioned geometries, not an additional detector measurement of the local +track `X` coordinate. + +### `calcPCACovMatrix()` + +The vertex covariance is + +```text +C_V = A^-1, A = sum_i R_i I_i R_i^T . +``` + +If `A` is singular or ill-conditioned, returning a small fallback covariance +would incorrectly shrink the uncertainty along the weakly constrained direction. +The conservative behavior is: + +1. Check that `A` is compatible with a positive-definite information matrix. +2. Reject cases where the determinant is too small compared with the diagonal + scale. +3. Invert only when these checks pass. +4. If inversion fails, or if the inverted covariance has non-positive diagonal + elements, return a deliberately loose fallback covariance. + +The code uses the leading-minor checks + +```text +A_XX > 0, +det(A_XY block) > 0, +det(A) > epsilon max(diag(A))^3 +``` + +with + +```text +epsilon = 1e-12 . +``` + +On failure, it returns a diagonal covariance with + +```text +sigma^2 = 1e6 cm^2 . +``` + +This corresponds to a 10 m uncertainty in each coordinate. It is intentionally +loose: the fallback marks the parent vertex as poorly constrained rather than +creating an artificially precise parent covariance. + +## 11. Code changes summarized + +1. `TrackCovI` now stores a full symmetric local 3D information matrix. +2. The dummy `XerrFactor` approximation was removed. +3. PCA weights and chi2 derivatives now use `sxx,sxy,sxz,syy,syz,szz`. +4. PCA covariance is the inverse of the summed global information matrix. +5. The Hessian residual-curvature term is restricted to diagonal Hessian + elements. +6. `correctTracks()` now propagates the actual candidate track state with O2's + analytic constant-`Bz` transport, keeping `mCandTr` and `mTrPos` + synchronized. +7. `createParentTrackParCov()` now propagates daughter momentum covariance from + native O2 `(snp,tgl,q/pt)` parameters to lab `(px,py,pz)` before constructing + the parent `TrackParCov`. diff --git a/Common/DCAFitter/include/DCAFitter/DCAFitterN.h b/Common/DCAFitter/include/DCAFitter/DCAFitterN.h index 2641dec84aed9..d02efa68526db 100644 --- a/Common/DCAFitter/include/DCAFitter/DCAFitterN.h +++ b/Common/DCAFitter/include/DCAFitter/DCAFitterN.h @@ -12,7 +12,8 @@ /// \file DCAFitterN.h /// \brief Defintions for N-prongs secondary vertex fit /// \author ruben.shahoyan@cern.ch -/// For the formulae derivation see /afs/cern.ch/user/s/shahoian/public/O2/DCAFitter/DCAFitterN.pdf +/// For the original derivation see /afs/cern.ch/user/s/shahoian/public/O2/DCAFitter/DCAFitterN.pdf +/// The AI-assisted readme is in DCAFitterN_derivation.md #ifndef _ALICEO2_DCA_FITTERN_ #define _ALICEO2_DCA_FITTERN_ @@ -28,17 +29,19 @@ namespace vertexing { ///__________________________________________________________________________________ -///< Inverse cov matrix (augmented by a dummy X error) of the point defined by the track +///< Inverse covariance matrix of the point defined by the track struct TrackCovI { - float sxx, syy, syz, szz; + // Independent elements of the symmetric 3D information matrix + // H^T Cyz^{-1} H. A track constrains Y and Z at a given X through + // H = {{-dY/dX, 1, 0}, {-dZ/dX, 0, 1}}. + float sxx, sxy, sxz, syy, syz, szz; GPUdDefault() TrackCovI() = default; - GPUd() bool set(const o2::track::TrackParCov& trc, float xerrFactor = 1.f) + GPUd() bool set(const o2::track::TrackParCov& trc) { - // we assign Y error to X for DCA calculation - // (otherwise for quazi-collinear tracks the X will not be constrained) - float cyy = trc.getSigmaY2(), czz = trc.getSigmaZ2(), cyz = trc.getSigmaZY(), cxx = cyy * xerrFactor; + // Invert the 2D covariance of the measured track position (Y,Z). + float cyy = trc.getSigmaY2(), czz = trc.getSigmaZ2(), cyz = trc.getSigmaZY(); float detYZ = cyy * czz - cyz * cyz; bool res = true; if (detYZ <= 0.) { @@ -47,10 +50,19 @@ struct TrackCovI { res = false; } auto detYZI = 1. / detYZ; - sxx = 1. / cxx; syy = czz * detYZI; syz = -cyz * detYZI; szz = cyy * detYZI; + const float cspI = 1.f / trc.getCsp(); + const float dydx = trc.getSnp() * cspI; + const float dzdx = trc.getTgl() * cspI; + sxy = -(syy * dydx + syz * dzdx); + sxz = -(syz * dydx + szz * dzdx); + sxx = dydx * dydx * syy + 2.f * dydx * dzdx * syz + dzdx * dzdx * szz; + // The matrix is degenerate by construction, regularize sxx term to preserve the original YZ block exactly + constexpr float XRegErrFactor = 10.f; + const float sigmaX2 = cyy * XRegErrFactor; + sxx += 1.f / sigmaX2; return res; } }; @@ -98,7 +110,6 @@ class DCAFitterN static constexpr double NMax = 4; static constexpr double NInv = 1. / N; static constexpr int MAXHYP = 2; - static constexpr float XerrFactor = 5.; // factor for conversion of track covYY to dummy covXX using Track = o2::track::TrackParCov; using TrackAuxPar = o2::track::TrackAuxPar; using CrossInfo = o2::track::CrossInfo; @@ -213,6 +224,7 @@ class DCAFitterN ///< recalculate PCA as a cov-matrix weighted mean, even if absDCA method was used GPUd() bool recalculatePCAWithErrors(int cand = 0); + GPUd() double calcCollinearInflation(int cand) const; GPUd() MatSym3D calcPCACovMatrix(int cand = 0) const; std::array calcPCACovMatrixFlat(int cand = 0) const @@ -313,17 +325,6 @@ class DCAFitterN return mat; } - GPUd() MatSym3D getTrackCovMatrix(int i, int cand = 0) const // generate covariance matrix of track position, adding fake X error - { - const auto& trc = mCandTr[mOrder[cand]][i]; - MatSym3D mat; - mat(0, 0) = trc.getSigmaY2() * XerrFactor; - mat(1, 1) = trc.getSigmaY2(); - mat(2, 2) = trc.getSigmaZ2(); - mat(2, 1) = trc.getSigmaZY(); - return mat; - } - GPUd() void assign(int) {} template GPUd() void assign(int i, const T& t, const Tr&... args) @@ -389,9 +390,10 @@ class DCAFitterN std::array mNIters; // number of iterations for each seed std::array mTrPropDone{}; // Flag that the tracks are fully propagated to PCA std::array mPropFailed{}; // Flag that some propagation failed for this PCA candidate - LogLogThrottler mLoggerBadCov{}; - LogLogThrottler mLoggerBadInv{}; - LogLogThrottler mLoggerBadProp{}; + mutable LogLogThrottler mLoggerBadCov{}; + mutable LogLogThrottler mLoggerBadInv{}; + mutable LogLogThrottler mLoggerBadProp{}; + mutable LogLogThrottler mLoggerBadPCACov{}; MatSym3D mWeightInv; // inverse weight of single track, [sum{M^T E M}]^-1 in EQ.T std::array mOrder{0}; int mCurHyp = 0; @@ -502,13 +504,13 @@ GPUd() bool DCAFitterN::calcPCACoefs() const auto& taux = mTrAux[i]; const auto& tcov = mTrcEInv[mCurHyp][i]; MatStd3D miei; - miei[0][0] = taux.c * tcov.sxx; - miei[0][1] = -taux.s * tcov.syy; - miei[0][2] = -taux.s * tcov.syz; - miei[1][0] = taux.s * tcov.sxx; - miei[1][1] = taux.c * tcov.syy; - miei[1][2] = taux.c * tcov.syz; - miei[2][0] = 0; + miei[0][0] = taux.c * tcov.sxx - taux.s * tcov.sxy; + miei[0][1] = taux.c * tcov.sxy - taux.s * tcov.syy; + miei[0][2] = taux.c * tcov.sxz - taux.s * tcov.syz; + miei[1][0] = taux.s * tcov.sxx + taux.c * tcov.sxy; + miei[1][1] = taux.s * tcov.sxy + taux.c * tcov.syy; + miei[1][2] = taux.s * tcov.sxz + taux.c * tcov.syz; + miei[2][0] = tcov.sxz; miei[2][1] = tcov.syz; miei[2][2] = tcov.szz; mTrCFVT[mCurHyp][i] = mWeightInv * miei; @@ -532,11 +534,11 @@ GPUd() bool DCAFitterN::calcInverseWeight() for (int i = N; i--;) { const auto& taux = mTrAux[i]; const auto& tcov = mTrcEInv[mCurHyp][i]; - arrmat[XX] += taux.cc * tcov.sxx + taux.ss * tcov.syy; - arrmat[XY] += taux.cs * (tcov.sxx - tcov.syy); - arrmat[XZ] += -taux.s * tcov.syz; - arrmat[YY] += taux.cc * tcov.syy + taux.ss * tcov.sxx; - arrmat[YZ] += taux.c * tcov.syz; + arrmat[XX] += taux.cc * tcov.sxx - 2. * taux.cs * tcov.sxy + taux.ss * tcov.syy; + arrmat[XY] += taux.cs * (tcov.sxx - tcov.syy) + (taux.cc - taux.ss) * tcov.sxy; + arrmat[XZ] += taux.c * tcov.sxz - taux.s * tcov.syz; + arrmat[YY] += taux.ss * tcov.sxx + 2. * taux.cs * tcov.sxy + taux.cc * tcov.syy; + arrmat[YZ] += taux.s * tcov.sxz + taux.c * tcov.syz; arrmat[ZZ] += tcov.szz; } // invert 3x3 symmetrix matrix @@ -670,9 +672,9 @@ GPUd() void DCAFitterN::calcChi2Derivatives() const auto& covI = mTrcEInv[mCurHyp][j]; // inverse cov matrix of track j const auto& dr1 = mDResidDx[j][i]; // vector of j-th residuals 1st derivative over X param of track i auto& cidr = covIDrDx[i][j]; // vector covI_j * dres_j/dx_i, save for 2nd derivative calculation - cidr[0] = covI.sxx * dr1[0]; - cidr[1] = covI.syy * dr1[1] + covI.syz * dr1[2]; - cidr[2] = covI.syz * dr1[1] + covI.szz * dr1[2]; + cidr[0] = covI.sxx * dr1[0] + covI.sxy * dr1[1] + covI.sxz * dr1[2]; + cidr[1] = covI.sxy * dr1[0] + covI.syy * dr1[1] + covI.syz * dr1[2]; + cidr[2] = covI.sxz * dr1[0] + covI.syz * dr1[1] + covI.szz * dr1[2]; // calculate res_i * covI_j * dres_j/dx_i dchi1 += o2::math_utils::Dot(res, cidr); } @@ -686,11 +688,13 @@ GPUd() void DCAFitterN::calcChi2Derivatives() const auto& dr1j = mDResidDx[k][j]; // vector of k-th residuals 1st derivative over X param of track j const auto& cidrkj = covIDrDx[i][k]; // vector covI_k * dres_k/dx_i dchi2 += o2::math_utils::Dot(dr1j, cidrkj); - if (k == j) { + if (i == j) { const auto& res = mTrRes[mCurHyp][k]; // vector of residuals of track k const auto& covI = mTrcEInv[mCurHyp][k]; // inverse cov matrix of track k - const auto& dr2ij = mD2ResidDx2[k][j]; // vector of k-th residuals 2nd derivative over X params of track j - dchi2 += res[0] * covI.sxx * dr2ij[0] + res[1] * (covI.syy * dr2ij[1] + covI.syz * dr2ij[2]) + res[2] * (covI.syz * dr2ij[1] + covI.szz * dr2ij[2]); + const auto& dr2ij = mD2ResidDx2[k][i]; // vector of k-th residuals 2nd derivative over X param i + dchi2 += res[0] * (covI.sxx * dr2ij[0] + covI.sxy * dr2ij[1] + covI.sxz * dr2ij[2]) + + res[1] * (covI.sxy * dr2ij[0] + covI.syy * dr2ij[1] + covI.syz * dr2ij[2]) + + res[2] * (covI.sxz * dr2ij[0] + covI.syz * dr2ij[1] + covI.szz * dr2ij[2]); } } } @@ -705,16 +709,23 @@ GPUd() void DCAFitterN::calcChi2DerivativesNoErr() for (int i = N; i--;) { auto& dchi1 = mDChi2Dx[i]; // DChi2/Dx_i = sum_j { res_j * Dres_j/Dx_i } dchi1 = 0; // chi2 1st derivative - for (int j = N; j--;) { - const auto& res = mTrRes[mCurHyp][j]; // vector of residuals of track j - const auto& dr1 = mDResidDx[j][i]; // vector of j-th residuals 1st derivative over X param of track i + for (int k = N; k--;) { + const auto& res = mTrRes[mCurHyp][k]; // vector of residuals of track k + const auto& dr1 = mDResidDx[k][i]; // vector of k-th residuals 1st derivative over X param of track i dchi1 += o2::math_utils::Dot(res, dr1); - if (i >= j) { // symmetrix matrix - // chi2 2nd derivative - auto& dchi2 = mD2Chi2Dx2[i][j]; // D2Chi2/Dx_i/Dx_j = sum_k { Dres_k/Dx_j * covI_k * Dres_k/Dx_i + res_k * covI_k * D2res_k/Dx_i/Dx_j } - dchi2 = o2::math_utils::Dot(mTrRes[mCurHyp][i], mD2ResidDx2[i][j]); - for (int k = N; k--;) { - dchi2 += o2::math_utils::Dot(mDResidDx[k][i], mDResidDx[k][j]); + } + } + for (int i = N; i--;) { + for (int j = i + 1; j--;) { + auto& dchi2 = mD2Chi2Dx2[i][j]; + dchi2 = 0.; + for (int k = N; k--;) { + // Gauss-Newton term, present for diagonal and mixed elements. + dchi2 += o2::math_utils::Dot(mDResidDx[k][i], mDResidDx[k][j]); + // A trajectory has a second derivative only with respect to its own + // X parameter, hence the curvature term contributes only to H_ii. + if (i == j) { + dchi2 += o2::math_utils::Dot(mTrRes[mCurHyp][k], mD2ResidDx2[k][i]); } } } @@ -744,7 +755,7 @@ GPUd() bool DCAFitterN::recalculatePCAWithErrors(int cand) mCurHyp = mOrder[cand]; if (mUseAbsDCA) { for (int i = N; i--;) { - if (!mTrcEInv[mCurHyp][i].set(mCandTr[mCurHyp][i], XerrFactor)) { // prepare inverse cov.matrices at starting point + if (!mTrcEInv[mCurHyp][i].set(mCandTr[mCurHyp][i])) { // prepare inverse cov.matrices at starting point if (mLoggerBadCov.needToLog()) { #ifndef GPUCA_GPUCODE printf("fitter %d: error (%ld muted): overrode invalid track covariance from %s\n", @@ -797,30 +808,106 @@ GPUd() void DCAFitterN::calcPCANoErr() //___________________________________________________________________ template -GPUd() o2::math_utils::SMatrix> DCAFitterN::calcPCACovMatrix(int cand) const +GPUd() double DCAFitterN::calcCollinearInflation(int cand) const { - // calculate covariance matrix for the point of closest approach - MatSym3D covm; - int nAdded = 0; - for (int i = N; i--;) { // calculate sum of inverses - // MatSym3D covTr = o2::math_utils::Similarity(mUseAbsDCA ? getTrackRotMatrix(i) : mTrCFVT[mOrder[cand]][i], getTrackCovMatrix(i, cand)); - // RS by using Similarity(mTrCFVT[mOrder[cand]][i], getTrackCovMatrix(i, cand)) we underestimate the error, use simple rotation - MatSym3D covTr = o2::math_utils::Similarity(getTrackRotMatrix(i), getTrackCovMatrix(i, cand)); - if (covTr.Invert()) { - covm += covTr; - nAdded++; + std::array, N> u{}; + int nu = 0; + + for (int i = 0; i < N; ++i) { + std::array p{}; + if (!getTrack(i, cand).getPxPyPzGlo(p)) { + continue; + } + const double p2 = p[0] * p[0] + p[1] * p[1] + p[2] * p[2]; + if (p2 <= 0.) { + continue; + } + const double pI = 1. / std::sqrt(p2); + u[nu++] = {p[0] * pI, p[1] * pI, p[2] * pI}; + } + + if (nu < 2) { + return 1.; + } + + double sin2Mean = 0.; + int npairs = 0; + for (int i = 0; i < nu; ++i) { + for (int j = i + 1; j < nu; ++j) { + double cij = u[i][0] * u[j][0] + u[i][1] * u[j][1] + u[i][2] * u[j][2]; + cij = std::clamp(cij, -1., 1.); + sin2Mean += std::max(0., 1. - cij * cij); + ++npairs; } } - if (nAdded && covm.Invert()) { - return covm; + sin2Mean /= npairs; + + constexpr double Sin2Ref = 1.e-5; + constexpr double MaxInflation = 1.e4; + if (sin2Mean <= 0.) { + return MaxInflation; } - // correct way has failed, use simple sum - MatSym3D covmSum; + return sin2Mean < Sin2Ref ? std::min(MaxInflation, Sin2Ref / sin2Mean) : 1.; +} + +//___________________________________________________________________ +template +GPUd() o2::math_utils::SMatrix> DCAFitterN::calcPCACovMatrix(int cand) const +{ + // Each track measures Y and Z at the vertex X. With the local slopes + // sy = dY/dX and sz = dZ/dX, its vertex measurement matrix is + // H = {{-sy, 1, 0}, {-sz, 0, 1}}. The longitudinal information must come + // from the track geometry, not from a dummy X variance. + MatSym3D info; + auto* arrmat = info.Array(); + memset(arrmat, 0, sizeof(info)); + enum { XX, + XY, + YY, + XZ, + YZ, + ZZ }; + const int ord = mOrder[cand]; for (int i = N; i--;) { - MatSym3D covTr = o2::math_utils::Similarity(getTrackRotMatrix(i), getTrackCovMatrix(i, cand)); - covmSum += covTr; + const auto& taux = mTrAux[i]; + TrackCovI tcov; + tcov.set(mCandTr[ord][i]); + arrmat[XX] += taux.cc * tcov.sxx - 2. * taux.cs * tcov.sxy + taux.ss * tcov.syy; + arrmat[XY] += taux.cs * (tcov.sxx - tcov.syy) + (taux.cc - taux.ss) * tcov.sxy; + arrmat[XZ] += taux.c * tcov.sxz - taux.s * tcov.syz; + arrmat[YY] += taux.ss * tcov.sxx + 2. * taux.cs * tcov.sxy + taux.cc * tcov.syy; + arrmat[YZ] += taux.s * tcov.sxz + taux.c * tcov.syz; + arrmat[ZZ] += tcov.szz; } - return covmSum; + const double maxDiag = o2::gpu::GPUCommonMath::Max(o2::gpu::GPUCommonMath::Max(info(0, 0), info(1, 1)), info(2, 2)); + const double det2 = info(0, 0) * info(1, 1) - info(1, 0) * info(1, 0); + const double det3 = info(0, 0) * (info(1, 1) * info(2, 2) - info(2, 1) * info(2, 1)) - + info(1, 0) * (info(1, 0) * info(2, 2) - info(2, 1) * info(2, 0)) + + info(2, 0) * (info(1, 0) * info(2, 1) - info(1, 1) * info(2, 0)); + constexpr double MinRelDet = 1.e-12; + constexpr double InflateRelDet = 1.e-6; + constexpr double MaxInflation = 1.e4; + const bool isWellConditionedInfo = maxDiag > 0. && info(0, 0) > 0. && det2 > 0. && det3 > MinRelDet * maxDiag * maxDiag * maxDiag; + if (isWellConditionedInfo) { + auto cov = info; + if (cov.Invert() && cov(0, 0) > 0. && cov(1, 1) > 0. && cov(2, 2) > 0.) { + // if (mIsCollinear) { + // cov *= calcCollinearInflation(cand); + // } + return cov; + } + } + if (mLoggerBadPCACov.needToLog()) { + printf("fitter %d: error (%ld muted): override ill-conditioned PCACovMatrix by dummy matrix", mFitterID, mLoggerBadPCACov.evCount); + } + // Fall back on a deliberately loose vertex covariance. Returning a tight + // identity covariance for a singular or ill-conditioned information matrix + // would shrink the uncertainty in the weakly constrained direction. + memset(arrmat, 0, sizeof(info)); + info(0, 0) = 4.; + info(1, 1) = 4.; + info(2, 2) = 4.; + return info; } //___________________________________________________________________ @@ -856,7 +943,8 @@ GPUdi() double DCAFitterN::calcChi2() const for (int i = N; i--;) { const auto& res = mTrRes[mCurHyp][i]; const auto& covI = mTrcEInv[mCurHyp][i]; - chi2 += res[0] * res[0] * covI.sxx + res[1] * res[1] * covI.syy + res[2] * res[2] * covI.szz + 2. * res[1] * res[2] * covI.syz; + chi2 += res[0] * res[0] * covI.sxx + res[1] * res[1] * covI.syy + res[2] * res[2] * covI.szz + + 2. * (res[0] * res[1] * covI.sxy + res[0] * res[2] * covI.sxz + res[1] * res[2] * covI.syz); } return chi2; } @@ -878,13 +966,26 @@ GPUdi() double DCAFitterN::calcChi2NoErr() const template GPUd() bool DCAFitterN::correctTracks(const VecND& corrX) { - // propagate tracks to updated X + // Propagate the actual candidate tracks to the updated X. Use the analytic + // constant-Bz transport here: Newton corrections are small, but the track + // state must stay synchronized with mTrPos for the next derivative update. for (int i = N; i--;) { - const auto& trDer = mTrDer[mCurHyp][i]; - auto dx2h = 0.5 * corrX[i] * corrX[i]; - mTrPos[mCurHyp][i][0] -= corrX[i]; - mTrPos[mCurHyp][i][1] -= trDer.dydx * corrX[i] - dx2h * trDer.d2ydx2; - mTrPos[mCurHyp][i][2] -= trDer.dzdx * corrX[i] - dx2h * trDer.d2zdx2; + /* + // Updating only mTrPos by Taylor expansion leaves mCandTr at the previous X, + // leaving calcTrackDerivatives() insensitive to the update. Use full fast propagation instead. + const auto& trDer = mTrDer[mCurHyp][i]; + auto dx2h = 0.5 * corrX[i] * corrX[i]; + mTrPos[mCurHyp][i][0] -= corrX[i]; + mTrPos[mCurHyp][i][1] -= trDer.dydx * corrX[i] - dx2h * trDer.d2ydx2; + mTrPos[mCurHyp][i][2] -= trDer.dzdx * corrX[i] - dx2h * trDer.d2zdx2; + */ + auto& trc = mCandTr[mCurHyp][i]; + const float x = static_cast(mTrPos[mCurHyp][i][0] - corrX[i]); + const bool propagated = mUseAbsDCA ? trc.propagateParamTo(x, mBz) : trc.propagateTo(x, mBz); + if (!propagated) { + return false; + } + setTrackPos(mTrPos[mCurHyp][i], trc); } return true; } @@ -972,7 +1073,7 @@ GPUd() bool DCAFitterN::minimizeChi2() return false; } setTrackPos(mTrPos[mCurHyp][i], mCandTr[mCurHyp][i]); // prepare positions - if (!mTrcEInv[mCurHyp][i].set(mCandTr[mCurHyp][i], XerrFactor)) { // prepare inverse cov.matrices at starting point + if (!mTrcEInv[mCurHyp][i].set(mCandTr[mCurHyp][i])) { // prepare inverse cov.matrices at starting point if (mLoggerBadCov.needToLog()) { #ifndef GPUCA_GPUCODE printf("fitter %d: error (%ld muted): overrode invalid track covariance from %s\n", @@ -1176,21 +1277,45 @@ GPUd() void DCAFitterN::print() const template GPUd() o2::track::TrackParCov DCAFitterN::createParentTrackParCov(int cand, bool sectorAlpha) const { - const auto& trP = getTrack(0, cand); - const auto& trN = getTrack(1, cand); std::array covV = {0.}; std::array pvecV = {0.}; int q = 0; for (int it = 0; it < N; it++) { const auto& trc = getTrack(it, cand); std::array pvecT = {0.}; - std::array covT = {0.}; - trc.getPxPyPzGlo(pvecT); - trc.getCovXYZPxPyPzGlo(covT); - constexpr int MomInd[6] = {9, 13, 14, 18, 19, 20}; // cov matrix elements for momentum component - for (int i = 0; i < 6; i++) { - covV[MomInd[i]] += covT[MomInd[i]]; + const bool hasMomentum = trc.getPxPyPzGlo(pvecT); + + // Propagate only the native momentum-parameter covariance + // (snp,tgl,q/pt) to the lab momentum covariance. The final constructor + // below rotates the summed lab covariance to the parent alpha frame. + if (hasMomentum) { + const double snp = trc.getSnp(); + const double csp = trc.getCsp(); + const double pt = trc.getPt(); + const double alpha = trc.getAlpha(); + double sna = 0., csa = 0.; + o2::math_utils::detail::sincos(alpha, sna, csa); + const double dPxdSnp = -pt * (snp * csa / csp + sna); + const double dPydSnp = pt * (csa - snp * sna / csp); + const double dPzdTgl = pt; + const double q2ptI = 1. / trc.getQ2Pt(); + const double dPxdQ = -pvecT[0] * q2ptI; + const double dPydQ = -pvecT[1] * q2ptI; + const double dPzdQ = -pvecT[2] * q2ptI; + const double cSnpSnp = trc.getSigmaSnp2(); + const double cTglSnp = trc.getSigmaTglSnp(); + const double cTglTgl = trc.getSigmaTgl2(); + const double cQSnp = trc.getSigma1PtSnp(); + const double cQTgl = trc.getSigma1PtTgl(); + const double cQQ = trc.getSigma1Pt2(); + covV[9] += dPxdSnp * dPxdSnp * cSnpSnp + 2. * dPxdSnp * dPxdQ * cQSnp + dPxdQ * dPxdQ * cQQ; + covV[13] += dPydSnp * (dPxdSnp * cSnpSnp + dPxdQ * cQSnp) + dPydQ * (dPxdSnp * cQSnp + dPxdQ * cQQ); + covV[14] += dPydSnp * dPydSnp * cSnpSnp + 2. * dPydSnp * dPydQ * cQSnp + dPydQ * dPydQ * cQQ; + covV[18] += dPzdTgl * (dPxdSnp * cTglSnp + dPxdQ * cQTgl) + dPzdQ * (dPxdSnp * cQSnp + dPxdQ * cQQ); + covV[19] += dPzdTgl * (dPydSnp * cTglSnp + dPydQ * cQTgl) + dPzdQ * (dPydSnp * cQSnp + dPydQ * cQQ); + covV[20] += dPzdTgl * dPzdTgl * cTglTgl + 2. * dPzdTgl * dPzdQ * cQTgl + dPzdQ * dPzdQ * cQQ; } + for (int i = 0; i < 3; i++) { pvecV[i] += pvecT[i]; } @@ -1245,9 +1370,9 @@ GPUdi() bool DCAFitterN::propagateParamToX(o2::track::TrackPar& t, f mPropFailed[mCurHyp] = true; if (mLoggerBadProp.needToLog()) { #ifndef GPUCA_GPUCODE - printf("fitter %d: error (%ld muted): propagation failed for %s\n", mFitterID, mLoggerBadProp.evCount, t.asString().c_str()); + printf("fitter %d: error (%ld muted): propagation to %.4f failed for %s\n", mFitterID, mLoggerBadProp.evCount, x, t.asString().c_str()); #else - printf("fitter %d: error (%ld muted): propagation failed\n", mFitterID, mLoggerBadProp.evCount); + printf("fitter %d: error (%ld muted): propagation to %.4f failed\n", mFitterID, mLoggerBadProp.evCount, x); #endif } } @@ -1271,9 +1396,9 @@ GPUdi() bool DCAFitterN::propagateToX(o2::track::TrackParCov& t, flo mPropFailed[mCurHyp] = true; if (mLoggerBadProp.needToLog()) { #ifndef GPUCA_GPUCODE - printf("fitter %d: error (%ld muted): propagation failed for %s\n", mFitterID, mLoggerBadProp.evCount, t.asString().c_str()); + printf("fitter %d: error (%ld muted): propagation to %.4f failed for %s\n", mFitterID, mLoggerBadProp.evCount, x, t.asString().c_str()); #else - printf("fitter %d: error (%ld muted): propagation failed\n", mFitterID, mLoggerBadProp.evCount); + printf("fitter %d: error (%ld muted): propagation to %.4f failed\n", mFitterID, mLoggerBadProp.evCount, x); #endif } } diff --git a/Common/DCAFitter/test/testDCAFitterN.cxx b/Common/DCAFitter/test/testDCAFitterN.cxx index bd00b5bed841e..8ab2c7f4ca323 100644 --- a/Common/DCAFitter/test/testDCAFitterN.cxx +++ b/Common/DCAFitter/test/testDCAFitterN.cxx @@ -56,11 +56,21 @@ float checkResults(o2::utils::TreeStreamRedirector& outs, std::string& treeName, double dst = TMath::Sqrt(df[0] * df[0] + df[1] * df[1] + df[2] * df[2]); distMin = dst < distMin ? dst : distMin; auto parentTrack = fitter.createParentTrackParCov(ic); - // float genX + const std::array genPos{static_cast(vgen[0]), static_cast(vgen[1]), static_cast(vgen[2])}; + const std::array genMom{static_cast(genPar.Px()), static_cast(genPar.Py()), static_cast(genPar.Pz())}; + o2::track::TrackPar genParentTrack(genPos, genMom, parentTrack.getCharge(), false); + genParentTrack.rotateParam(parentTrack.getAlpha()); + std::array parentCovGlo{}; + const bool hasParentCovGlo = parentTrack.getCovXYZPxPyPzGlo(parentCovGlo); + const double pullX = hasParentCovGlo && parentCovGlo[0] > 0.f ? df[0] / TMath::Sqrt(parentCovGlo[0]) : 0.; + const double pullY = hasParentCovGlo && parentCovGlo[2] > 0.f ? df[1] / TMath::Sqrt(parentCovGlo[2]) : 0.; + const double pullZ = hasParentCovGlo && parentCovGlo[5] > 0.f ? df[2] / TMath::Sqrt(parentCovGlo[5]) : 0.; outs << treeName.c_str() << "cand=" << ic << "ncand=" << nCand << "nIter=" << nIter << "chi2=" << chi2 << "genPart=" << genPar << "recPart=" << moth << "genX=" << vgen[0] << "genY=" << vgen[1] << "genZ=" << vgen[2] << "dx=" << df[0] << "dy=" << df[1] << "dz=" << df[2] << "dst=" << dst + << "pullX=" << pullX << "pullY=" << pullY << "pullZ=" << pullZ + << "genParentTrack=" << genParentTrack << "useAbsDCA=" << absDCA << "useWghDCA=" << useWghDCA << "parent=" << parentTrack; for (int i = 0; i < fitter.getNProngs(); i++) { outs << treeName.c_str() << fmt::format("prong{}=", i).c_str() << fitter.getTrack(i, ic); diff --git a/Common/MathUtils/include/MathUtils/detail/trigonometric.h b/Common/MathUtils/include/MathUtils/detail/trigonometric.h index 68d002320df2e..e13d965663dc9 100644 --- a/Common/MathUtils/include/MathUtils/detail/trigonometric.h +++ b/Common/MathUtils/include/MathUtils/detail/trigonometric.h @@ -298,10 +298,10 @@ GPUhdi() constexpr T fastATan2(T y, T x) T tan = 0; if (xx < 0) { // p1 is in the range [Pi/4, 3*Pi/4] phi0 = Pi075; - tan = -x1 / y1; + tan = y1 > T(0) ? -x1 / y1 : T(0); // yy is always >=0, hence y1>=0 } else { // p1 is in the range [-Pi/4, Pi/4] phi0 = Pi025; - tan = y1 / x1; + tan = x1 > T(0) ? y1 / x1 : T(0); } return phi0 + atan(tan); }; diff --git a/Common/SimConfig/include/SimConfig/SimConfig.h b/Common/SimConfig/include/SimConfig/SimConfig.h index be88d9fbd8c33..0be82ba1921b5 100644 --- a/Common/SimConfig/include/SimConfig/SimConfig.h +++ b/Common/SimConfig/include/SimConfig/SimConfig.h @@ -88,8 +88,9 @@ struct SimConfigData { bool mForwardKine = false; // true if tracks and event headers are to be published on a FairMQ channel (for reading by other consumers) bool mWriteToDisc = true; // whether we write simulation products (kine, hits) to disc VertexMode mVertexMode = VertexMode::kDiamondParam; // by default we should use die InteractionDiamond parameter + std::string mExtGeomFile = ""; // optional path to a JSON file describing external (CAD) geometry modules to inject - ClassDefNV(SimConfigData, 4); + ClassDefNV(SimConfigData, 5); }; // A singleton class which can be used @@ -178,6 +179,7 @@ class SimConfig bool forwardKine() const { return mConfigData.mForwardKine; } bool writeToDisc() const { return mConfigData.mWriteToDisc; } VertexMode getVertexMode() const { return mConfigData.mVertexMode; } + std::string getExtGeomFilename() const { return mConfigData.mExtGeomFile; } // returns the pair of collision context filename as well as event prefix encoded // in the mFromCollisionContext string. Returns empty string if information is not available or set. diff --git a/Common/SimConfig/src/SimConfig.cxx b/Common/SimConfig/src/SimConfig.cxx index 15879687872d5..14f99dd4ea580 100644 --- a/Common/SimConfig/src/SimConfig.cxx +++ b/Common/SimConfig/src/SimConfig.cxx @@ -75,7 +75,8 @@ void SimConfig::initOptions(boost::program_options::options_description& options "asservice", bpo::value()->default_value(false), "run in service/server mode")( "noGeant", bpo::bool_switch(), "prohibits any Geant transport/physics (by using tight cuts)")( "forwardKine", bpo::bool_switch(), "forward kinematics on a FairMQ channel")( - "noDiscOutput", bpo::bool_switch(), "switch off writing sim results to disc (useful in combination with forwardKine)"); + "noDiscOutput", bpo::bool_switch(), "switch off writing sim results to disc (useful in combination with forwardKine)")( + "extGeomFile", bpo::value()->default_value(""), "Path to a JSON file describing external (CAD) geometry modules to inject (see Detectors/Passive ExternalModule). Modules are added when their 'name' is part of the active module list."); options.add_options()("fromCollContext", bpo::value()->default_value(""), "Use a pregenerated collision context to infer number of events to simulate, how to embedd them, the vertex position etc. Takes precedence of other options such as \"--nEvents\". The format is COLLISIONCONTEXTFILE.root[:SIGNALNAME] where SIGNALNAME is the event part in the context which is relevant."); } @@ -354,6 +355,7 @@ bool SimConfig::resetFromParsedMap(boost::program_options::variables_map const& if (vm.count("noemptyevents")) { mConfigData.mFilterNoHitEvents = true; } + mConfigData.mExtGeomFile = vm["extGeomFile"].as(); mConfigData.mFromCollisionContext = vm["fromCollContext"].as(); auto collcontext_simprefix = getCollContextFilenameAndEventPrefix(); adjustFromCollContext(collcontext_simprefix.first, collcontext_simprefix.second); diff --git a/Common/Utils/include/CommonUtils/ConfigurableParam.h b/Common/Utils/include/CommonUtils/ConfigurableParam.h index 0a6da30aa39f5..fa4acd2744703 100644 --- a/Common/Utils/include/CommonUtils/ConfigurableParam.h +++ b/Common/Utils/include/CommonUtils/ConfigurableParam.h @@ -407,6 +407,8 @@ class ConfigurableParam // writes a human readable INI or JSON file depending on the extension static void write(std::string const& filename, std::string const& keyOnly = ""); + static std::string asJSON(std::string const& keyOnly = ""); + // can be used instead of using API on concrete child classes template static T getValueAs(std::string key) @@ -494,6 +496,9 @@ class ConfigurableParam // be updated, absence of data for any of requested params will lead to fatal static void updateFromFile(std::string const&, std::string const& paramsList = "", bool unchangedOnly = false); + // update from a JSON string with the same filtering semantics as updateFromFile + static void updateFromJSONString(std::string const&, std::string const& paramsList = "", bool unchangedOnly = false); + // interface for use from the CCDB API; allows to sync objects read from CCDB with the information // stored in the registry; modifies given object as well as registry virtual void syncCCDBandRegistry(void* obj) = 0; diff --git a/Common/Utils/src/ConfigurableParam.cxx b/Common/Utils/src/ConfigurableParam.cxx index 202433b04e4c4..4de478a6d0e9f 100644 --- a/Common/Utils/src/ConfigurableParam.cxx +++ b/Common/Utils/src/ConfigurableParam.cxx @@ -37,6 +37,7 @@ #endif #include #include +#include #include #include #include @@ -754,6 +755,29 @@ void ConfigurableParam::writeJSON(std::string const& filename, std::string const // ------------------------------------------------------------------ +std::string ConfigurableParam::asJSON(std::string const& keyOnly) +{ + initPropertyTree(); // update the boost tree before writing + std::ostringstream os; + if (!keyOnly.empty()) { // write ini for selected key only + try { + boost::property_tree::ptree kTree; + auto keys = o2::utils::Str::tokenize(keyOnly, " ,;", true, true); + for (const auto& k : keys) { + kTree.add_child(k, sPtree->get_child(k)); + } + boost::property_tree::write_json(os, kTree); + } catch (const boost::property_tree::ptree_bad_path& err) { + LOG(fatal) << "non-existing key " << keyOnly << " provided to writeJSON"; + } + } else { + boost::property_tree::write_json(os, *sPtree); + } + return os.str(); +} + +// ------------------------------------------------------------------ + void ConfigurableParam::initPropertyTree() { sPtree->clear(); @@ -870,26 +894,10 @@ void ConfigurableParam::printAllRegisteredParamNames() // ------------------------------------------------------------------ -// Update the storage map of params from the given configuration file. -// It can be in JSON or INI format. -// If nonempty comma-separated paramsList is provided, only those params will -// be updated, absence of data for any of requested params will lead to fatal -// If unchangedOnly is true, then only those parameters whose provenance is kCODE will be updated -// (to allow preference of run-time settings) -void ConfigurableParam::updateFromFile(std::string const& configFile, std::string const& paramsList, bool unchangedOnly) +namespace +{ +void updateFromPropertyTree(boost::property_tree::ptree const& pt, std::string const& source, std::string const& paramsList, bool unchangedOnly) { - if (!sIsFullyInitialized) { - initialize(); - } - - auto cfgfile = o2::utils::Str::trim_copy(configFile); - - if (cfgfile.length() == 0) { - return; - } - - boost::property_tree::ptree pt = ConfigurableParamReaders::readConfigFile(cfgfile); - std::vector> keyValPairs; auto request = o2::utils::Str::tokenize(paramsList, ',', true); std::unordered_map requestMap; @@ -913,7 +921,7 @@ void ConfigurableParam::updateFromFile(std::string const& configFile, std::strin auto name = subKey.first; auto value = subKey.second.get_value(); std::string key = mainKey + "." + name; - if (!unchangedOnly || getProvenance(key) == kCODE) { + if (!unchangedOnly || ConfigurableParam::getProvenance(key) == ConfigurableParam::kCODE) { std::pair pair = std::make_pair(key, o2::utils::Str::trim_copy(value)); keyValPairs.push_back(pair); } @@ -928,16 +936,62 @@ void ConfigurableParam::updateFromFile(std::string const& configFile, std::strin // make sure all requested params were retrieved for (const auto& req : requestMap) { if (req.second == 0) { - throw std::runtime_error(fmt::format("Param {:s} was not found in {:s}", req.first, configFile)); + throw std::runtime_error(fmt::format("Param {:s} was not found in {:s}", req.first, source)); } } try { - setValues(keyValPairs); + ConfigurableParam::setValues(keyValPairs); } catch (std::exception const& error) { LOG(error) << "Error while setting values " << error.what(); } } +} // namespace + +// Update the storage map of params from the given configuration file. +// It can be in JSON or INI format. +// If nonempty comma-separated paramsList is provided, only those params will +// be updated, absence of data for any of requested params will lead to fatal +// If unchangedOnly is true, then only those parameters whose provenance is kCODE will be updated +// (to allow preference of run-time settings) +void ConfigurableParam::updateFromFile(std::string const& configFile, std::string const& paramsList, bool unchangedOnly) +{ + if (!sIsFullyInitialized) { + initialize(); + } + + auto cfgfile = o2::utils::Str::trim_copy(configFile); + + if (cfgfile.length() == 0) { + return; + } + + updateFromPropertyTree(ConfigurableParamReaders::readConfigFile(cfgfile), configFile, paramsList, unchangedOnly); +} + +// ------------------------------------------------------------------ + +void ConfigurableParam::updateFromJSONString(std::string const& configJSON, std::string const& paramsList, bool unchangedOnly) +{ + if (!sIsFullyInitialized) { + initialize(); + } + + auto json = o2::utils::Str::trim_copy(configJSON); + if (json.length() == 0) { + return; + } + + boost::property_tree::ptree pt; + std::istringstream input(json); + try { + boost::property_tree::read_json(input, pt); + } catch (const boost::property_tree::ptree_error& e) { + LOG(fatal) << "Failed to read JSON config string (" << e.what() << ")"; + } + + updateFromPropertyTree(pt, "provided JSON string", paramsList, unchangedOnly); +} // ------------------------------------------------------------------ // ------------------------------------------------------------------ diff --git a/Common/Utils/test/testConfigurableParam.cxx b/Common/Utils/test/testConfigurableParam.cxx index f0c3c59c79c35..6fd0344cdd1be 100644 --- a/Common/Utils/test/testConfigurableParam.cxx +++ b/Common/Utils/test/testConfigurableParam.cxx @@ -151,6 +151,42 @@ BOOST_AUTO_TEST_CASE(ConfigurableParam_FileIO_Json) std::remove(testFileName.c_str()); } +BOOST_AUTO_TEST_CASE(ConfigurableParam_JSONString_UnchangedOnly) +{ + ConfigurableParam::setValue("TestParam.ulValue", "2"); + ConfigurableParam::setProvenance("TestParam", "lValue", ConfigurableParam::kCODE); + ConfigurableParam::setProvenance("TestParam", "ulValue", ConfigurableParam::kRT); + ConfigurableParam::updateFromJSONString(R"json({"TestParam":{"lValue":"77","ulValue":"88"}})json", "TestParam", true); + + BOOST_CHECK_EQUAL(TestParam::Instance().lValue, 77); + BOOST_CHECK_EQUAL(TestParam::Instance().ulValue, 2); +} + +BOOST_AUTO_TEST_CASE(ConfigurableParam_JSONString_FromAsJSON) +{ + ConfigurableParam::setValue("TestParam.iValue", "321"); + ConfigurableParam::setValue("TestParam.sValue", "json-source"); + ConfigurableParam::setValues({{"TestParam.map", "{7:8,9:10}"}}); + const auto mapBefore = TestParam::Instance().map; + const auto json = ConfigurableParam::asJSON("TestParam"); + + ConfigurableParam::setValue("TestParam.iValue", "999"); + ConfigurableParam::setValue("TestParam.sValue", "json-modified"); + ConfigurableParam::setValues({{"TestParam.map", "{1:2}"}}); + ConfigurableParam::updateFromJSONString(json, "TestParam"); + + BOOST_CHECK_EQUAL(TestParam::Instance().iValue, 321); + BOOST_CHECK_EQUAL(TestParam::Instance().sValue, "json-source"); + BOOST_CHECK_EQUAL(TestParam::Instance().map.size(), mapBefore.size()); + BOOST_CHECK_EQUAL(TestParam::Instance().map.at(7), mapBefore.at(7)); + BOOST_CHECK_EQUAL(TestParam::Instance().map.at(9), mapBefore.at(9)); +} + +BOOST_AUTO_TEST_CASE(ConfigurableParam_JSONString_ParamsListMissing) +{ + BOOST_CHECK_THROW(ConfigurableParam::updateFromJSONString(ConfigurableParam::asJSON("TestParam"), "MissingParam"), std::runtime_error); +} + BOOST_AUTO_TEST_CASE(ConfigurableParam_FileIO_ROOT) { // test for root file serialization diff --git a/DataFormats/Detectors/Common/include/DetectorsCommonDataFormats/SimTraits.h b/DataFormats/Detectors/Common/include/DetectorsCommonDataFormats/SimTraits.h index 37c4b790d181b..72782d4ed7bdf 100644 --- a/DataFormats/Detectors/Common/include/DetectorsCommonDataFormats/SimTraits.h +++ b/DataFormats/Detectors/Common/include/DetectorsCommonDataFormats/SimTraits.h @@ -124,6 +124,10 @@ namespace itsmft { class Hit; } +namespace trkft3 +{ +class Hit; +} namespace tof { class HitType; @@ -246,11 +250,11 @@ struct DetIDToHitTypes { }; template <> struct DetIDToHitTypes { - using HitType = o2::itsmft::Hit; + using HitType = o2::trkft3::Hit; }; template <> struct DetIDToHitTypes { - using HitType = o2::itsmft::Hit; + using HitType = o2::trkft3::Hit; }; template <> struct DetIDToHitTypes { diff --git a/DataFormats/Detectors/Upgrades/ALICE3/CMakeLists.txt b/DataFormats/Detectors/Upgrades/ALICE3/CMakeLists.txt index 360b50d442d7d..3914b8c7ede8d 100644 --- a/DataFormats/Detectors/Upgrades/ALICE3/CMakeLists.txt +++ b/DataFormats/Detectors/Upgrades/ALICE3/CMakeLists.txt @@ -10,4 +10,4 @@ # or submit itself to any jurisdiction. add_subdirectory(FD3) -add_subdirectory(TRK) +add_subdirectory(TRKFT3) diff --git a/DataFormats/Detectors/Upgrades/ALICE3/TRK/CMakeLists.txt b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/CMakeLists.txt similarity index 57% rename from DataFormats/Detectors/Upgrades/ALICE3/TRK/CMakeLists.txt rename to DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/CMakeLists.txt index c239a2a36845d..fd6e02c44a6b6 100644 --- a/DataFormats/Detectors/Upgrades/ALICE3/TRK/CMakeLists.txt +++ b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/CMakeLists.txt @@ -9,16 +9,4 @@ # granted to it by virtue of its status as an Intergovernmental Organization # or submit itself to any jurisdiction. -o2_add_library(DataFormatsTRK - SOURCES src/Cluster.cxx - src/ROFRecord.cxx - PUBLIC_LINK_LIBRARIES O2::CommonDataFormat - O2::DataFormatsITSMFT - O2::SimulationDataFormat -) - -o2_target_root_dictionary(DataFormatsTRK - HEADERS include/DataFormatsTRK/Cluster.h - include/DataFormatsTRK/ROFRecord.h - LINKDEF src/DataFormatsTRKLinkDef.h -) +add_subdirectory(common) diff --git a/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/CMakeLists.txt b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/CMakeLists.txt new file mode 100644 index 0000000000000..d2a8b73da3455 --- /dev/null +++ b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/CMakeLists.txt @@ -0,0 +1,24 @@ +# Copyright 2019-2026 CERN and copyright holders of ALICE O2. +# See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +# All rights not expressly granted are reserved. +# +# This software is distributed under the terms of the GNU General Public +# License v3 (GPL Version 3), copied verbatim in the file "COPYING". +# +# In applying this license CERN does not waive the privileges and immunities +# granted to it by virtue of its status as an Intergovernmental Organization +# or submit itself to any jurisdiction. + +o2_add_library(DataFormatsTRKFT3 + SOURCES src/Digit.cxx + src/Hit.cxx + src/ROFRecord.cxx + PUBLIC_LINK_LIBRARIES O2::CommonDataFormat + O2::SimulationDataFormat) + +o2_target_root_dictionary(DataFormatsTRKFT3 + HEADERS include/DataFormatsTRKFT3/Cluster.h + include/DataFormatsTRKFT3/Digit.h + include/DataFormatsTRKFT3/Hit.h + include/DataFormatsTRKFT3/ROFRecord.h + LINKDEF src/DataFormatsTRKFT3LinkDef.h) diff --git a/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/include/DataFormatsTRKFT3/Cluster.h b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/include/DataFormatsTRKFT3/Cluster.h new file mode 100644 index 0000000000000..c3517f289d505 --- /dev/null +++ b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/include/DataFormatsTRKFT3/Cluster.h @@ -0,0 +1,52 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef ALICEO2_DATAFORMATSTRKFT3_CLUSTER_H +#define ALICEO2_DATAFORMATSTRKFT3_CLUSTER_H + +#include "DetectorsCommonDataFormats/DetID.h" +#include +#include +#include +#include + +namespace o2::trkft3 +{ + +template +struct Cluster { + static_assert(DetID == o2::detectors::DetID::TRK || DetID == o2::detectors::DetID::FT3, "only TRK and FT3 clusters are supported"); + + uint16_t chipID = 0; + uint16_t row = 0; + uint16_t col = 0; + uint16_t size = 1; + int16_t subDetID = -1; + int16_t layer = -1; + + std::string asString() const + { + std::ostringstream stream; + stream << o2::detectors::DetID(DetID).getName() << " cluster chip=" << chipID + << " row=" << row << " col=" << col << " size=" << size + << " subDet=" << subDetID << " layer=" << layer; + return stream.str(); + } + + ClassDefNV(Cluster, 1); +}; + +using TRKCluster = Cluster; +using FT3Cluster = Cluster; + +} // namespace o2::trkft3 + +#endif diff --git a/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/include/DataFormatsTRKFT3/Digit.h b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/include/DataFormatsTRKFT3/Digit.h new file mode 100644 index 0000000000000..41aa774a2ea06 --- /dev/null +++ b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/include/DataFormatsTRKFT3/Digit.h @@ -0,0 +1,60 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef ALICEO2_DATAFORMATSTRKFT3_DIGIT_H +#define ALICEO2_DATAFORMATSTRKFT3_DIGIT_H + +#include "Rtypes.h" +#include +#include + +namespace o2::trkft3 +{ + +class Digit +{ + public: + Digit(UShort_t chipindex = 0, UShort_t row = 0, UShort_t col = 0, Int_t charge = 0); + ~Digit() = default; + + UShort_t getChipIndex() const { return mChipIndex; } + UShort_t getColumn() const { return mCol; } + UShort_t getRow() const { return mRow; } + Int_t getCharge() const { return mCharge; } + + void setChipIndex(UShort_t index) { mChipIndex = index; } + void setPixelIndex(UShort_t row, UShort_t col) + { + mRow = row; + mCol = col; + } + void setCharge(Int_t charge) { mCharge = charge < USHRT_MAX ? charge : USHRT_MAX; } + void addCharge(int charge) { setCharge(charge + int(mCharge)); } + + std::ostream& print(std::ostream& output) const; + friend std::ostream& operator<<(std::ostream& output, const Digit& digi) + { + digi.print(output); + return output; + } + + private: + UShort_t mChipIndex = 0; + UShort_t mRow = 0; + UShort_t mCol = 0; + UShort_t mCharge = 0; + + ClassDefNV(Digit, 1); +}; + +} // namespace o2::trkft3 + +#endif diff --git a/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/include/DataFormatsTRKFT3/Hit.h b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/include/DataFormatsTRKFT3/Hit.h new file mode 100644 index 0000000000000..bcc51828436f7 --- /dev/null +++ b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/include/DataFormatsTRKFT3/Hit.h @@ -0,0 +1,111 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef ALICEO2_DATAFORMATSTRKFT3_HIT_H +#define ALICEO2_DATAFORMATSTRKFT3_HIT_H + +#include +#include + +#include "CommonUtils/ShmAllocator.h" +#include "SimulationDataFormat/BaseHits.h" +#include "Rtypes.h" +#include "TVector3.h" + +namespace o2::trkft3 +{ + +class Hit : public o2::BasicXYZEHit +{ + public: + enum HitStatus_t { + kTrackEntering = 0x1, + kTrackInside = 0x1 << 1, + kTrackExiting = 0x1 << 2, + kTrackOut = 0x1 << 3, + kTrackStopped = 0x1 << 4, + kTrackAlive = 0x1 << 5 + }; + + Hit() = default; + + Hit(int trackID, unsigned short detID, const TVector3& startPos, const TVector3& endPos, const TVector3& startMom, + double startE, double endTime, double eLoss, unsigned char startStatus, unsigned char endStatus); + + math_utils::Point3D GetPosStart() const { return mPosStart; } + Float_t GetStartX() const { return mPosStart.X(); } + Float_t GetStartY() const { return mPosStart.Y(); } + Float_t GetStartZ() const { return mPosStart.Z(); } + template + void GetStartPosition(F& x, F& y, F& z) const + { + x = GetStartX(); + y = GetStartY(); + z = GetStartZ(); + } + + math_utils::Vector3D GetMomentum() const { return mMomentum; } + math_utils::Vector3D& GetMomentum() { return mMomentum; } + Float_t GetPx() const { return mMomentum.X(); } + Float_t GetPy() const { return mMomentum.Y(); } + Float_t GetPz() const { return mMomentum.Z(); } + Float_t GetE() const { return mE; } + Float_t GetTotalEnergy() const { return GetE(); } + + UChar_t GetStatusEnd() const { return mTrackStatusEnd; } + UChar_t GetStatusStart() const { return mTrackStatusStart; } + + Bool_t IsEntering() const { return mTrackStatusEnd & kTrackEntering; } + Bool_t IsInside() const { return mTrackStatusEnd & kTrackInside; } + Bool_t IsExiting() const { return mTrackStatusEnd & kTrackExiting; } + Bool_t IsOut() const { return mTrackStatusEnd & kTrackOut; } + Bool_t IsStopped() const { return mTrackStatusEnd & kTrackStopped; } + Bool_t IsAlive() const { return mTrackStatusEnd & kTrackAlive; } + + Bool_t IsEnteringStart() const { return mTrackStatusStart & kTrackEntering; } + Bool_t IsInsideStart() const { return mTrackStatusStart & kTrackInside; } + Bool_t IsExitingStart() const { return mTrackStatusStart & kTrackExiting; } + Bool_t IsOutStart() const { return mTrackStatusStart & kTrackOut; } + Bool_t IsStoppedStart() const { return mTrackStatusStart & kTrackStopped; } + Bool_t IsAliveStart() const { return mTrackStatusStart & kTrackAlive; } + + void SetPosStart(const math_utils::Point3D& p) { mPosStart = p; } + + void Print(const Option_t* opt) const; + friend std::ostream& operator<<(std::ostream& of, const Hit& point) + { + of << "-I- Hit: O2 trkft3 point for track " << point.GetTrackID() << " in detector " << point.GetDetectorID() << std::endl; + return of; + } + + private: + math_utils::Vector3D mMomentum; ///< momentum at entrance + math_utils::Point3D mPosStart; ///< position at entrance, base position is at exit + Float_t mE = 0.f; ///< total energy at entrance + UChar_t mTrackStatusEnd = 0; ///< MC status flag at exit + UChar_t mTrackStatusStart = 0; ///< MC status at starting point + + ClassDefNV(Hit, 3); +}; + +} // namespace o2::trkft3 + +#ifdef USESHM +namespace std +{ +template <> +class allocator : public o2::utils::ShmAllocator +{ +}; +} // namespace std +#endif + +#endif diff --git a/DataFormats/Detectors/Upgrades/ALICE3/TRK/include/DataFormatsTRK/ROFRecord.h b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/include/DataFormatsTRKFT3/ROFRecord.h similarity index 78% rename from DataFormats/Detectors/Upgrades/ALICE3/TRK/include/DataFormatsTRK/ROFRecord.h rename to DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/include/DataFormatsTRKFT3/ROFRecord.h index 86ee31389fd5f..633ad7e4af24d 100644 --- a/DataFormats/Detectors/Upgrades/ALICE3/TRK/include/DataFormatsTRK/ROFRecord.h +++ b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/include/DataFormatsTRKFT3/ROFRecord.h @@ -9,8 +9,8 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#ifndef ALICEO2_DATAFORMATSTRK_ROFRECORD_H -#define ALICEO2_DATAFORMATSTRK_ROFRECORD_H +#ifndef ALICEO2_DATAFORMATSTRKFT3_ROFRECORD_H +#define ALICEO2_DATAFORMATSTRKFT3_ROFRECORD_H #include "CommonDataFormat/InteractionRecord.h" #include "CommonDataFormat/RangeReference.h" @@ -18,7 +18,7 @@ #include #include -namespace o2::trk +namespace o2::trkft3 { class ROFRecord @@ -56,20 +56,6 @@ class ROFRecord ClassDefNV(ROFRecord, 1); }; -struct MC2ROFRecord { - using ROFtype = unsigned int; - - int eventRecordID = -1; - int rofRecordID = 0; - ROFtype minROF = 0; - ROFtype maxROF = 0; - - MC2ROFRecord() = default; - MC2ROFRecord(int evID, int rofRecID, ROFtype mnrof, ROFtype mxrof) : eventRecordID(evID), rofRecordID(rofRecID), minROF(mnrof), maxROF(mxrof) {} - - ClassDefNV(MC2ROFRecord, 1); -}; - -} // namespace o2::trk +} // namespace o2::trkft3 #endif diff --git a/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/src/DataFormatsTRKFT3LinkDef.h b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/src/DataFormatsTRKFT3LinkDef.h new file mode 100644 index 0000000000000..046f680e42e8a --- /dev/null +++ b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/src/DataFormatsTRKFT3LinkDef.h @@ -0,0 +1,29 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifdef __CLING__ + +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ class o2::trkft3::Digit + ; +#pragma link C++ class std::vector < o2::trkft3::Digit> + ; +#pragma link C++ class o2::trkft3::Hit + ; +#pragma link C++ class std::vector < o2::trkft3::Hit> + ; +#pragma link C++ class o2::trkft3::Cluster < o2::detectors::DetID::TRK> + ; +#pragma link C++ class std::vector < o2::trkft3::Cluster < o2::detectors::DetID::TRK>> + ; +#pragma link C++ class o2::trkft3::Cluster < o2::detectors::DetID::FT3> + ; +#pragma link C++ class std::vector < o2::trkft3::Cluster < o2::detectors::DetID::FT3>> + ; +#pragma link C++ class o2::trkft3::ROFRecord + ; +#pragma link C++ class std::vector < o2::trkft3::ROFRecord> + ; + +#endif diff --git a/DataFormats/Detectors/Upgrades/ALICE3/TRK/include/DataFormatsTRK/Cluster.h b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/src/Digit.cxx similarity index 55% rename from DataFormats/Detectors/Upgrades/ALICE3/TRK/include/DataFormatsTRK/Cluster.h rename to DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/src/Digit.cxx index ec68191b3c43f..f01d98d1e005d 100644 --- a/DataFormats/Detectors/Upgrades/ALICE3/TRK/include/DataFormatsTRK/Cluster.h +++ b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/src/Digit.cxx @@ -9,30 +9,21 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#ifndef ALICEO2_DATAFORMATSTRK_CLUSTER_H -#define ALICEO2_DATAFORMATSTRK_CLUSTER_H +#include "DataFormatsTRKFT3/Digit.h" +#include -#include -#include -#include +ClassImp(o2::trkft3::Digit); -namespace o2::trk -{ - -struct Cluster { - uint16_t chipID = 0; - uint16_t row = 0; - uint16_t col = 0; - uint16_t size = 1; - int16_t subDetID = -1; - int16_t layer = -1; - int16_t disk = -1; - - std::string asString() const; +using namespace o2::trkft3; - ClassDefNV(Cluster, 1); -}; - -} // namespace o2::trk +Digit::Digit(UShort_t chipindex, UShort_t row, UShort_t col, Int_t charge) + : mChipIndex(chipindex), mRow(row), mCol(col) +{ + setCharge(charge); +} -#endif +std::ostream& Digit::print(std::ostream& output) const +{ + output << "TRKFT3Digit chip [" << mChipIndex << "] R:" << mRow << " C:" << mCol << " Q: " << mCharge; + return output; +} diff --git a/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/src/Hit.cxx b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/src/Hit.cxx new file mode 100644 index 0000000000000..ff99214cbd994 --- /dev/null +++ b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/src/Hit.cxx @@ -0,0 +1,41 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include "DataFormatsTRKFT3/Hit.h" + +#include + +ClassImp(o2::trkft3::Hit); + +namespace o2::trkft3 +{ + +Hit::Hit(int trackID, unsigned short detID, const TVector3& startPos, const TVector3& endPos, const TVector3& startMom, + double startE, double endTime, double eLoss, unsigned char startStatus, unsigned char endStatus) + : BasicXYZEHit(endPos.X(), endPos.Y(), endPos.Z(), endTime, eLoss, trackID, detID), + mMomentum(startMom.Px(), startMom.Py(), startMom.Pz()), + mPosStart(startPos.X(), startPos.Y(), startPos.Z()), + mE(startE), + mTrackStatusEnd(endStatus), + mTrackStatusStart(startStatus) +{ +} + +void Hit::Print(const Option_t* opt) const +{ + printf( + "Det: %5d Track: %6d E.loss: %.3e P: %+.3e %+.3e %+.3e\n" + "PosIn: %+.3e %+.3e %+.3e PosOut: %+.3e %+.3e %+.3e\n", + GetDetectorID(), GetTrackID(), GetEnergyLoss(), GetPx(), GetPy(), GetPz(), + GetStartX(), GetStartY(), GetStartZ(), GetX(), GetY(), GetZ()); +} + +} // namespace o2::trkft3 diff --git a/DataFormats/Detectors/Upgrades/ALICE3/TRK/src/ROFRecord.cxx b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/src/ROFRecord.cxx similarity index 85% rename from DataFormats/Detectors/Upgrades/ALICE3/TRK/src/ROFRecord.cxx rename to DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/src/ROFRecord.cxx index 79745f9854eb7..9b2808653cf99 100644 --- a/DataFormats/Detectors/Upgrades/ALICE3/TRK/src/ROFRecord.cxx +++ b/DataFormats/Detectors/Upgrades/ALICE3/TRKFT3/common/src/ROFRecord.cxx @@ -9,13 +9,12 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#include "DataFormatsTRK/ROFRecord.h" +#include "DataFormatsTRKFT3/ROFRecord.h" #include -ClassImp(o2::trk::ROFRecord); -ClassImp(o2::trk::MC2ROFRecord); +ClassImp(o2::trkft3::ROFRecord); -namespace o2::trk +namespace o2::trkft3 { std::string ROFRecord::asString() const @@ -26,4 +25,4 @@ std::string ROFRecord::asString() const return stream.str(); } -} // namespace o2::trk +} // namespace o2::trkft3 diff --git a/DataFormats/Reconstruction/include/ReconstructionDataFormats/PrimaryVertexExt.h b/DataFormats/Reconstruction/include/ReconstructionDataFormats/PrimaryVertexExt.h index a228984f2ae5d..4fd36ed248677 100644 --- a/DataFormats/Reconstruction/include/ReconstructionDataFormats/PrimaryVertexExt.h +++ b/DataFormats/Reconstruction/include/ReconstructionDataFormats/PrimaryVertexExt.h @@ -14,6 +14,7 @@ #include "ReconstructionDataFormats/PrimaryVertex.h" #include "ReconstructionDataFormats/GlobalTrackID.h" +#include "SimulationDataFormat/MCEventLabel.h" namespace o2 { @@ -27,6 +28,7 @@ struct PrimaryVertexExt : public PrimaryVertex { std::array nSrc{}; // N contributors for each source type std::array nSrcA{}; // N associated and passing cuts for each source type std::array nSrcAU{}; // N ambgous associated and passing cuts for each source type + o2::MCEventLabel mcLb{}; double FT0Time = -1.; // time of closest FT0 trigger float FT0A = -1; // amplitude of closest FT0 A side float FT0C = -1; // amplitude of closest FT0 C side @@ -41,7 +43,7 @@ struct PrimaryVertexExt : public PrimaryVertex { std::string asString() const; #endif - ClassDefNV(PrimaryVertexExt, 6); + ClassDefNV(PrimaryVertexExt, 7); }; #ifndef GPUCA_GPUCODE_DEVICE diff --git a/DataFormats/Reconstruction/src/TrackParametrizationWithError.cxx b/DataFormats/Reconstruction/src/TrackParametrizationWithError.cxx index ee2e96736aa6d..eb8071ec0073d 100644 --- a/DataFormats/Reconstruction/src/TrackParametrizationWithError.cxx +++ b/DataFormats/Reconstruction/src/TrackParametrizationWithError.cxx @@ -527,8 +527,9 @@ GPUd() void TrackParametrizationWithError::set(const dim3_t& xyz, const math_utils::detail::rotateZ(ver, -alp); math_utils::detail::rotateZ(mom, -alp); // - value_t pt = gpu::CAMath::Sqrt(mom[0] * mom[0] + mom[1] * mom[1]); - value_t ptI = 1.f / pt; + const value_t pt2 = mom[0] * mom[0] + mom[1] * mom[1]; + const value_t pt = gpu::CAMath::Sqrt(pt2); + const value_t ptI = 1.f / pt; this->setX(ver[0]); this->setAlpha(alp); this->setY(ver[1]); @@ -545,89 +546,53 @@ GPUd() void TrackParametrizationWithError::set(const dim3_t& xyz, const this->setSnp(-1.f + kSafe); // Protection } // - // Covariance matrix (formulas to be simplified) - value_t r = mom[0] * ptI; // cos(phi) - value_t cv34 = gpu::CAMath::Sqrt(cv[3] * cv[3] + cv[4] * cv[4]); - // - int special = 0; - value_t sgcheck = r * sn + this->getSnp() * cs; - if (gpu::CAMath::Abs(sgcheck) > 1 - kSafe) { // special case: lab phi is +-pi/2 - special = 1; - sgcheck = sgcheck < 0 ? -1.f : 1.f; - } else if (gpu::CAMath::Abs(sgcheck) < kSafe) { - sgcheck = cs < 0 ? -1.0f : 1.0f; - special = 2; // special case: lab phi is 0 + // Covariance matrix from the fixed-alpha Jacobian + // d(Y,Z,snp,tgl,q/pt) / d(X,Y,Z,Px,Py,Pz). + const value_t pt3I = ptI / pt2; + const value_t qeff = charge ? static_cast(charge) : 1.f; + + value_t cLab[6][6] = {}; + int idx = 0; + for (int i = 0; i < 6; ++i) { + for (int j = 0; j <= i; ++j) { + cLab[i][j] = cLab[j][i] = cv[idx++]; + } } - // - mC[kSigY2] = cv[0] + cv[2]; - mC[kSigZY] = (-cv[3] * sn) < 0 ? -cv34 : cv34; - mC[kSigZ2] = cv[5]; - // - value_t ptI2 = ptI * ptI; - value_t tgl2 = this->getTgl() * this->getTgl(); - if (special == 1) { - mC[kSigSnpY] = cv[6] * ptI; - mC[kSigSnpZ] = -sgcheck * cv[8] * r * ptI; - mC[kSigSnp2] = gpu::CAMath::Abs(cv[9] * r * r * ptI2); - mC[kSigTglY] = (cv[10] * this->getTgl() - sgcheck * cv[15]) * ptI / r; - mC[kSigTglZ] = (cv[17] - sgcheck * cv[12] * this->getTgl()) * ptI; - mC[kSigTglSnp] = (-sgcheck * cv[18] + cv[13] * this->getTgl()) * r * ptI2; - mC[kSigTgl2] = gpu::CAMath::Abs(cv[20] - 2 * sgcheck * cv[19] * mC[4] + cv[14] * tgl2) * ptI2; - mC[kSigQ2PtY] = cv[10] * ptI2 / r * charge; - mC[kSigQ2PtZ] = -sgcheck * cv[12] * ptI2 * charge; - mC[kSigQ2PtSnp] = cv[13] * r * ptI * ptI2 * charge; - mC[kSigQ2PtTgl] = (-sgcheck * cv[19] + cv[14] * this->getTgl()) * r * ptI2 * ptI; - mC[kSigQ2Pt2] = gpu::CAMath::Abs(cv[14] * ptI2 * ptI2); - } else if (special == 2) { - mC[kSigSnpY] = -cv[10] * ptI * cs / sn; - mC[kSigSnpZ] = cv[12] * cs * ptI; - mC[kSigSnp2] = gpu::CAMath::Abs(cv[14] * cs * cs * ptI2); - mC[kSigTglY] = (sgcheck * cv[6] * this->getTgl() - cv[15]) * ptI / sn; - mC[kSigTglZ] = (cv[17] - sgcheck * cv[8] * this->getTgl()) * ptI; - mC[kSigTglSnp] = (cv[19] - sgcheck * cv[13] * this->getTgl()) * cs * ptI2; - mC[kSigTgl2] = gpu::CAMath::Abs(cv[20] - 2 * sgcheck * cv[18] * this->getTgl() + cv[9] * tgl2) * ptI2; - mC[kSigQ2PtY] = sgcheck * cv[6] * ptI2 / sn * charge; - mC[kSigQ2PtZ] = -sgcheck * cv[8] * ptI2 * charge; - mC[kSigQ2PtSnp] = -sgcheck * cv[13] * cs * ptI * ptI2 * charge; - mC[kSigQ2PtTgl] = (-sgcheck * cv[18] + cv[9] * this->getTgl()) * ptI2 * ptI * charge; - mC[kSigQ2Pt2] = gpu::CAMath::Abs(cv[9] * ptI2 * ptI2); - } else { - double m00 = -sn; // m10=cs; - double m23 = -pt * (sn + this->getSnp() * cs / r), m43 = -pt * pt * (r * cs - this->getSnp() * sn); - double m24 = pt * (cs - this->getSnp() * sn / r), m44 = -pt * pt * (r * sn + this->getSnp() * cs); - double m35 = pt, m45 = -pt * pt * this->getTgl(); - // - if (charge) { // RS: this is a hack, proper treatment to be implemented - m43 *= charge; - m44 *= charge; - m45 *= charge; + + value_t jac[5][6] = {}; + jac[kY][0] = -sn; + jac[kY][1] = cs; + jac[kZ][2] = 1.; + + const value_t u = mom[0]; + const value_t v = mom[1]; + const value_t w = mom[2]; + const value_t dSnpDu = -u * v * pt3I; + const value_t dSnpDv = u * u * pt3I; + const value_t dTglDu = -w * u * pt3I; + const value_t dTglDv = -w * v * pt3I; + const value_t dTglDw = ptI; + const value_t dQ2PtDu = -qeff * u * pt3I; + const value_t dQ2PtDv = -qeff * v * pt3I; + + jac[kSnp][3] = dSnpDu * cs - dSnpDv * sn; + jac[kSnp][4] = dSnpDu * sn + dSnpDv * cs; + jac[kTgl][3] = dTglDu * cs - dTglDv * sn; + jac[kTgl][4] = dTglDu * sn + dTglDv * cs; + jac[kTgl][5] = dTglDw; + jac[kQ2Pt][3] = dQ2PtDu * cs - dQ2PtDv * sn; + jac[kQ2Pt][4] = dQ2PtDu * sn + dQ2PtDv * cs; + + for (int i = 0; i < kNParams; ++i) { + for (int j = 0; j <= i; ++j) { + value_t cij = 0.; + for (int k = 0; k < 6; ++k) { + for (int l = 0; l < 6; ++l) { + cij += jac[i][k] * cLab[k][l] * jac[j][l]; + } + } + mC[CovarMap[i][j]] = cij; } - // - double a1 = cv[13] - cv[9] * (m23 * m44 + m43 * m24) / m23 / m43; - double a2 = m23 * m24 - m23 * (m23 * m44 + m43 * m24) / m43; - double a3 = m43 * m44 - m43 * (m23 * m44 + m43 * m24) / m23; - double a4 = cv[14] + 2. * cv[9]; - double a5 = m24 * m24 - 2. * m24 * m44 * m23 / m43; - double a6 = m44 * m44 - 2. * m24 * m44 * m43 / m23; - // - mC[kSigSnpY] = (cv[10] * m43 - cv[6] * m44) / (m24 * m43 - m23 * m44) / m00; - mC[kSigQ2PtY] = (cv[6] / m00 - mC[kSigSnpY] * m23) / m43; - mC[kSigTglY] = (cv[15] / m00 - mC[kSigQ2PtY] * m45) / m35; - mC[kSigSnpZ] = (cv[12] * m43 - cv[8] * m44) / (m24 * m43 - m23 * m44); - mC[kSigQ2PtZ] = (cv[8] - mC[kSigSnpZ] * m23) / m43; - mC[kSigTglZ] = cv[17] / m35 - mC[kSigQ2PtZ] * m45 / m35; - mC[kSigSnp2] = gpu::CAMath::Abs((a4 * a3 - a6 * a1) / (a5 * a3 - a6 * a2)); - mC[kSigQ2Pt2] = gpu::CAMath::Abs((a1 - a2 * mC[kSigSnp2]) / a3); - mC[kSigQ2PtSnp] = (cv[9] - mC[kSigSnp2] * m23 * m23 - mC[kSigQ2Pt2] * m43 * m43) / m23 / m43; - double b1 = cv[18] - mC[kSigQ2PtSnp] * m23 * m45 - mC[kSigQ2Pt2] * m43 * m45; - double b2 = m23 * m35; - double b3 = m43 * m35; - double b4 = cv[19] - mC[kSigQ2PtSnp] * m24 * m45 - mC[kSigQ2Pt2] * m44 * m45; - double b5 = m24 * m35; - double b6 = m44 * m35; - mC[kSigTglSnp] = (b4 - b6 * b1 / b3) / (b5 - b6 * b2 / b3); - mC[kSigQ2PtTgl] = b1 / b3 - b2 * mC[kSigTglSnp] / b3; - mC[kSigTgl2] = gpu::CAMath::Abs((cv[20] - mC[kSigQ2Pt2] * (m45 * m45) - mC[kSigQ2PtTgl] * 2.f * m35 * m45) / (m35 * m35)); } checkCovariance(); } @@ -1668,42 +1633,52 @@ GPUd() bool TrackParametrizationWithError::getCovXYZPxPyPzGlo(std::arra return false; } - auto pt = this->getPt(); - value_t sn, cs; + const value_t pt = this->getPt(); + const value_t q2pt = this->getQ2Pt(); + value_t sn = 0.f, cs = 0.f; o2::math_utils::detail::sincos(this->getAlpha(), sn, cs); - auto r = gpu::CAMath::Sqrt((1. - this->getSnp()) * (1. + this->getSnp())); - auto m00 = -sn, m10 = cs; - auto m23 = -pt * (sn + this->getSnp() * cs / r), m43 = -pt * pt * (r * cs - this->getSnp() * sn); - auto m24 = pt * (cs - this->getSnp() * sn / r), m44 = -pt * pt * (r * sn + this->getSnp() * cs); - auto m35 = pt, m45 = -pt * pt * this->getTgl(); - - if (this->getSign() < 0) { - m43 = -m43; - m44 = -m44; - m45 = -m45; - } - - cv[0] = mC[0] * m00 * m00; - cv[1] = mC[0] * m00 * m10; - cv[2] = mC[0] * m10 * m10; - cv[3] = mC[1] * m00; - cv[4] = mC[1] * m10; - cv[5] = mC[2]; - cv[6] = m00 * (mC[3] * m23 + mC[10] * m43); - cv[7] = m10 * (mC[3] * m23 + mC[10] * m43); - cv[8] = mC[4] * m23 + mC[11] * m43; - cv[9] = m23 * (mC[5] * m23 + mC[12] * m43) + m43 * (mC[12] * m23 + mC[14] * m43); - cv[10] = m00 * (mC[3] * m24 + mC[10] * m44); - cv[11] = m10 * (mC[3] * m24 + mC[10] * m44); - cv[12] = mC[4] * m24 + mC[11] * m44; - cv[13] = m23 * (mC[5] * m24 + mC[12] * m44) + m43 * (mC[12] * m24 + mC[14] * m44); - cv[14] = m24 * (mC[5] * m24 + mC[12] * m44) + m44 * (mC[12] * m24 + mC[14] * m44); - cv[15] = m00 * (mC[6] * m35 + mC[10] * m45); - cv[16] = m10 * (mC[6] * m35 + mC[10] * m45); - cv[17] = mC[7] * m35 + mC[11] * m45; - cv[18] = m23 * (mC[8] * m35 + mC[12] * m45) + m43 * (mC[13] * m35 + mC[14] * m45); - cv[19] = m24 * (mC[8] * m35 + mC[12] * m45) + m44 * (mC[13] * m35 + mC[14] * m45); - cv[20] = m35 * (mC[9] * m35 + mC[13] * m45) + m45 * (mC[13] * m35 + mC[14] * m45); + const value_t snp = this->getSnp(); + const value_t csp = gpu::CAMath::Sqrt((1.f - snp) * (1.f + snp)); + const value_t pXLoc = pt * csp; + const value_t pYLoc = pt * snp; + const value_t pZ = pt * this->getTgl(); + const value_t pX = cs * pXLoc - sn * pYLoc; + const value_t pY = sn * pXLoc + cs * pYLoc; + + value_t cTr[5][5] = {}; + for (int i = 0; i < kNParams; ++i) { + for (int j = 0; j <= i; ++j) { + cTr[i][j] = cTr[j][i] = mC[CovarMap[i][j]]; + } + } + + double jac[6][5] = {}; + jac[0][kY] = -sn; + jac[1][kY] = cs; + jac[2][kZ] = 1.f; + + const value_t dPxDSnp = -pt * (cs * snp / csp + sn); + const value_t dPyDSnp = pt * (cs - sn * snp / csp); + jac[3][kSnp] = dPxDSnp; + jac[4][kSnp] = dPyDSnp; + jac[5][kTgl] = pt; + + jac[3][kQ2Pt] = -pX / q2pt; + jac[4][kQ2Pt] = -pY / q2pt; + jac[5][kQ2Pt] = -pZ / q2pt; + + int idx = 0; + for (int i = 0; i < 6; ++i) { + for (int j = 0; j <= i; ++j) { + double cij = 0.f; + for (int k = 0; k < kNParams; ++k) { + for (int l = 0; l < kNParams; ++l) { + cij += jac[i][k] * cTr[k][l] * jac[j][l]; + } + } + cv[idx++] = cij; + } + } return true; } diff --git a/DataFormats/simulation/include/SimulationDataFormat/O2DatabasePDG.h b/DataFormats/simulation/include/SimulationDataFormat/O2DatabasePDG.h index 9111f548bb9d4..7a19798674bf6 100644 --- a/DataFormats/simulation/include/SimulationDataFormat/O2DatabasePDG.h +++ b/DataFormats/simulation/include/SimulationDataFormat/O2DatabasePDG.h @@ -408,14 +408,14 @@ inline void O2DatabasePDG::addALICEParticles(TDatabasePDG* db) ionCode = 1060020020; if (!db->GetParticle(ionCode)) { - db->AddParticle("OmegaOmega", "OmegaOmega", 3.229, kFALSE, - 2.5e-15, 6, "Special", ionCode); + db->AddParticle("OmegaOmega", "OmegaOmega", 3.343, kFALSE, + 8.01e-15, 6, "Special", ionCode); } ionCode = -1060020020; if (!db->GetParticle(ionCode)) { - db->AddParticle("AntiOmegaOmega", "AntiOmegaOmega", 3.229, kFALSE, - 2.5e-15, 6, "Special", ionCode); + db->AddParticle("AntiOmegaOmega", "AntiOmegaOmega", 3.343, kFALSE, + 8.01e-15, 6, "Special", ionCode); } ionCode = 1010010021; diff --git a/Detectors/AOD/include/AODProducerWorkflow/AODProducerWorkflowSpec.h b/Detectors/AOD/include/AODProducerWorkflow/AODProducerWorkflowSpec.h index 02f1b2582d74b..21728e78a7a20 100644 --- a/Detectors/AOD/include/AODProducerWorkflow/AODProducerWorkflowSpec.h +++ b/Detectors/AOD/include/AODProducerWorkflow/AODProducerWorkflowSpec.h @@ -246,6 +246,8 @@ class AODProducerWorkflowDPL : public Task return std::uint64_t(mStartIR.toLong()) + relativeTime_to_LocalBC(relativeTimeStampInNS); } + bool collectConfigFiles(std::vector& keys, std::vector& values, int indent = -1); + bool mThinTracks{false}; bool mPropTracks{false}; bool mPropMuons{false}; @@ -280,6 +282,7 @@ class AODProducerWorkflowDPL : public Task bool mEnableFITextra = false; bool mEnableTRDextra = false; bool mFieldON = false; + bool mCollectConfigFiles = false; const float cSpeed = 0.029979246f; // speed of light in TOF units GID::mask_t mInputSources; diff --git a/Detectors/AOD/src/AODProducerWorkflowSpec.cxx b/Detectors/AOD/src/AODProducerWorkflowSpec.cxx index 73b852d550ffe..385a9930d8f66 100644 --- a/Detectors/AOD/src/AODProducerWorkflowSpec.cxx +++ b/Detectors/AOD/src/AODProducerWorkflowSpec.cxx @@ -54,6 +54,8 @@ #include "Framework/TableBuilder.h" #include "Framework/CCDBParamSpec.h" #include "CommonUtils/TreeStreamRedirector.h" +#include "CommonUtils/KeyValParam.h" +#include "CommonUtils/NameConf.h" #include "FT0Base/Geometry.h" #include "GlobalTracking/MatchTOF.h" #include "ReconstructionDataFormats/Cascade.h" @@ -88,6 +90,7 @@ #include "MathUtils/Utils.h" #include "Math/SMatrix.h" #include "TString.h" +#include #include #include #include @@ -1902,6 +1905,8 @@ void AODProducerWorkflowDPL::init(InitContext& ic) mUseSigFiltMC = ic.options().get("mc-signal-filt"); + mCollectConfigFiles = ic.options().get("collect-config-files"); + // set no truncation if selected by user if (mTruncate != 1) { LOG(info) << "Truncation is not used!"; @@ -2670,6 +2675,10 @@ void AODProducerWorkflowDPL::run(ProcessingContext& pc) mMetaDataVals = {dataType, "3", O2Version, ROOTVersion, mRecoPass, mAnchorProd, mAnchorPass, mLPMProdTag, mUser}; add_additional_meta_info(mMetaDataKeys, mMetaDataVals); + if (mCollectConfigFiles) { + collectConfigFiles(mMetaDataKeys, mMetaDataVals); + } + pc.outputs().snapshot(Output{"AMD", "AODMetadataKeys", 0}, mMetaDataKeys); pc.outputs().snapshot(Output{"AMD", "AODMetadataVals", 0}, mMetaDataVals); @@ -3405,6 +3414,102 @@ std::vector AODProducerWorkflowDPL::fillBCFlags(const o2::globaltrackin return flags; } +bool AODProducerWorkflowDPL::collectConfigFiles(std::vector& keys, std::vector& values, int indent) +{ + // collect JSON-files of ConfigParams dumped by different upstream processors and add to medata + static std::string pattern, directory; + static size_t cachedNumberOfFiles = 0, cachedTotalFileSize = 0; + static bool first = true, discard = false; + if (discard) { + return false; + } + std::error_code ec; + if (first) { + first = false; + pattern = o2::base::NameConf::Instance().getConfigOutputFileName("*"); + auto dir = o2::conf::KeyValParam::Instance().getOutputDir(); + if (dir == "/dev/null") { + LOGP(warn, "ConfigParams output is disabled, abandoning {} files collection for metadata", pattern); + discard = true; + return false; + } + directory = (dir.empty() || dir == "none") ? "." : dir; + if (!std::filesystem::is_directory(directory, ec)) { + LOGP(error, R"(No directory "{}" is found to look for {} configuration files)", directory, pattern); + discard = true; + return false; + } + } + static std::unordered_map cachedMap; + std::vector files; + size_t currentTotalFileSize = 0; + + for (const auto& entry : std::filesystem::directory_iterator(directory)) { + if (!entry.is_regular_file()) { + continue; + } + const std::string fileName = entry.path().filename().string(); + if (fnmatch(pattern.c_str(), fileName.c_str(), 0) != 0) { + continue; + } + const auto fileSize = entry.file_size(ec); + if (ec) { + LOGP(error, "Cannot determine size of file {}, reason: {}", entry.path().string(), ec.message()); + } + files.push_back(entry.path()); + currentTotalFileSize += static_cast(fileSize); + } + + if (files.size() != cachedNumberOfFiles || currentTotalFileSize != cachedTotalFileSize) { // need to create a new map + cachedNumberOfFiles = files.size(); + cachedTotalFileSize = currentTotalFileSize; + cachedMap.clear(); + } + + if (!files.empty() && cachedMap.empty()) { + for (const auto& fname : files) { + std::ifstream input(fname); + if (!input) { + LOGP(error, "Cannot open JSON file {}", fname.string()); + cachedTotalFileSize = 0; // will trigger a new trial next time + continue; + } + nlohmann::json document; + try { + input >> document; + } catch (const nlohmann::json::parse_error& e) { + LOGP(error, "Cannot parse JSON file {}, reason: {}", fname.string(), e.what()); + cachedTotalFileSize = 0; // will trigger a new trial next time + continue; + } + + if (!document.is_object()) { + LOGP(error, "Top-level JSON value is not an object in file: {}", fname.string()); + cachedTotalFileSize = 0; // will trigger a new trial next time + continue; + } + + for (auto it = document.begin(); it != document.end(); ++it) { + const std::string& key = it.key(); + if (cachedMap.find(key) != cachedMap.end()) { + LOGP(error, "Duplicate top-level key {} in file {}", key, fname.string()); + continue; + } + LOGP(info, "Adding json config {} from file {} to AOD metadata", key, fname.string()); + nlohmann::json valueDocument = nlohmann::json::object(); + valueDocument[key] = it.value(); + cachedMap[key] = valueDocument.dump(indent); + } + } + } + + for (const auto& kv : cachedMap) { + keys.push_back(kv.first.c_str()); + values.push_back(kv.second.c_str()); + } + return true; +} + void AODProducerWorkflowDPL::endOfStream(EndOfStreamContext& /*ec*/) { LOGF(info, "aod producer dpl total timing: Cpu: %.3e Real: %.3e s in %d slots", @@ -3565,7 +3670,9 @@ DataProcessorSpec getAODProducerWorkflowSpec(GID::mask_t src, bool enableSV, boo ConfigParamSpec{"trackqc-tpc-pt", VariantType::Float, 0.2f, {"Keep TPC standalone track with this pt"}}, ConfigParamSpec{"with-streamers", VariantType::String, "", {"Bit-mask to steer writing of intermediate streamer files"}}, ConfigParamSpec{"seed", VariantType::Int, 0, {"Set seed for random generator used for sampling (0 (default) means using a random_device)"}}, - ConfigParamSpec{"mc-signal-filt", VariantType::Bool, false, {"Enable usage of signal filtering (only for MC with embedding)"}}}}; + ConfigParamSpec{"mc-signal-filt", VariantType::Bool, false, {"Enable usage of signal filtering (only for MC with embedding)"}}, + ConfigParamSpec{"collect-config-files", VariantType::Bool, false, {"Collect ConfigParams json files written by upsteam processors"}}, + }}; } } // namespace o2::aodproducer diff --git a/Detectors/Base/CMakeLists.txt b/Detectors/Base/CMakeLists.txt index 83a9193274e4f..e2d8114bc30fa 100644 --- a/Detectors/Base/CMakeLists.txt +++ b/Detectors/Base/CMakeLists.txt @@ -10,7 +10,13 @@ # or submit itself to any jurisdiction. #add_compile_options(-O0 -g -fPIC) +# Optional VecGeom backend for material-budget LUT filling, off by default when TGeo2VecGeom +# isn't installed (guarded by O2_WITH_VECGEOM). Linked PRIVATE: VecGeom types never appear in +# DetectorsBase's public headers, so consumers need neither VecGeom headers nor VecGeom itself. +find_package(TGeo2VecGeom CONFIG QUIET) + o2_add_library(DetectorsBase + TARGETVARNAME targetDetectorsBase SOURCES src/Detector.cxx src/GeometryManager.cxx src/MaterialManager.cxx @@ -31,6 +37,7 @@ o2_add_library(DetectorsBase src/GlobalParams.cxx src/O2Tessellated.cxx src/TGeoGeometryUtils.cxx + src/CADGeometryUtils.cxx PUBLIC_LINK_LIBRARIES FairRoot::Base O2::CommonUtils O2::DetectorsCommonDataFormats @@ -51,6 +58,13 @@ o2_add_library(DetectorsBase ROOT::Gdml ) +if(TGeo2VecGeom_FOUND) + target_compile_definitions(${targetDetectorsBase} PRIVATE O2_WITH_VECGEOM) + target_link_libraries(${targetDetectorsBase} PRIVATE TGeo2VecGeom::TGeo2VecGeom) +else() + message(STATUS "TGeo2VecGeom not found: DetectorsBase built without the optional VecGeom material-budget backend") +endif() + o2_target_root_dictionary(DetectorsBase HEADERS include/DetectorsBase/Detector.h include/DetectorsBase/GeometryManager.h @@ -92,6 +106,7 @@ if(BUILD_SIMULATION) endif() install(FILES test/buildMatBudLUT.C + test/compareMatBudLUT.C test/extractLUTLayers.C test/rescaleLUT.C DESTINATION share/macro/) @@ -100,6 +115,10 @@ o2_add_test_root_macro(test/buildMatBudLUT.C PUBLIC_LINK_LIBRARIES O2::DetectorsBase LABELS detectorsbase) +o2_add_test_root_macro(test/compareMatBudLUT.C + PUBLIC_LINK_LIBRARIES O2::DetectorsBase + LABELS detectorsbase) + o2_add_test_root_macro(test/extractLUTLayers.C PUBLIC_LINK_LIBRARIES O2::DetectorsBase LABELS detectorsbase) diff --git a/Detectors/Base/include/DetectorsBase/CADGeometryUtils.h b/Detectors/Base/include/DetectorsBase/CADGeometryUtils.h new file mode 100644 index 0000000000000..12d626d0eb572 --- /dev/null +++ b/Detectors/Base/include/DetectorsBase/CADGeometryUtils.h @@ -0,0 +1,50 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file CADGeometryUtils.h +/// \brief Helpers to inject CAD-derived (TGeo) geometry into O2 simulation +/// +/// These utilities are shared between purely passive external modules +/// (o2::passive::ExternalModule) and sensitive external detectors +/// (o2::ext::ExternalDetector). They deal with the geometry produced by +/// scripts/geometry/O2_CADtoTGeo.py, which is emitted as a ROOT macro. + +#ifndef ALICEO2_BASE_CADGEOMETRYUTILS_H +#define ALICEO2_BASE_CADGEOMETRYUTILS_H + +#include + +class TGeoVolume; + +namespace o2::base +{ + +/// JIT-compile a CAD-derived ROOT geometry macro (as produced by O2_CADtoTGeo.py) +/// and execute it to obtain the top TGeoVolume of the described module. +/// +/// The macro body is wrapped into a unique namespace (derived from \a instanceTag) +/// so that several such macros — which all export identically named symbols +/// (build(), get_builder_hook_unchecked(), ...) — can coexist in the same Cling +/// session without colliding. Returns nullptr on failure. +/// +/// \param macroFile path to the geometry macro (shell variables are expanded) +/// \param instanceTag a short tag used to build a unique, human-readable namespace +TGeoVolume* buildCADVolumeFromMacro(const std::string& macroFile, const std::string& instanceTag); + +/// Re-register the TGeo media used in the volume tree rooted at \a top into the O2 +/// MaterialManager under ownership of \a modulename, rewiring the volumes to the +/// newly created media. This brings the CAD-imported media under O2's media/cut +/// handling (so that e.g. tracking cuts apply consistently). +void remapCADMedia(TGeoVolume* top, const char* modulename); + +} // namespace o2::base + +#endif diff --git a/Detectors/Base/include/DetectorsBase/Detector.h b/Detectors/Base/include/DetectorsBase/Detector.h index f1744086d6a05..54e264dd22666 100644 --- a/Detectors/Base/include/DetectorsBase/Detector.h +++ b/Detectors/Base/include/DetectorsBase/Detector.h @@ -530,9 +530,14 @@ class DetImpl : public o2::base::Detector { using Hit_t = typename std::remove_pointer(this)->Det::getHits(0))>::type; using Collector_t = tbb::concurrent_unordered_map>>>; - static Collector_t hitcollector; // note: we can't put this as member because - // decltype type deduction doesn't seem to work for class members; so we use a static member - // and will use some pointer member to communicate this data to other functions + // note: we can't put this as a member because decltype type deduction doesn't seem to work for + // class members; so we use a static and communicate it to other functions via a pointer member. + // The collector must be kept *per detector instance* (keyed by 'this'): for most detectors there + // is a single instance per C++ type, but several external detectors share the same type + // (o2::ext::ExternalDetector) and would otherwise clobber/double-free each other's buffers. + // tbb::concurrent_unordered_map is node-based, so the reference stays valid across insertions. + static tbb::concurrent_unordered_map hitcollectors; + auto& hitcollector = hitcollectors[this]; mHitCollectorBufferPtr = (char*)&hitcollector; int probe = 0; diff --git a/Detectors/Base/include/DetectorsBase/GeometryManager.h b/Detectors/Base/include/DetectorsBase/GeometryManager.h index ad1d77b10f49a..f105d137c8742 100644 --- a/Detectors/Base/include/DetectorsBase/GeometryManager.h +++ b/Detectors/Base/include/DetectorsBase/GeometryManager.h @@ -29,6 +29,7 @@ #include class TGeoHMatrix; // lines 11-11 class TGeoManager; // lines 9-9 +class TGeoNavigator; namespace o2 { @@ -39,6 +40,12 @@ class AlignParam; namespace base { +/// Backend used to compute material budget for LUT filling: ROOT/TGeo (default, always +/// available) or VecGeom (requires O2 to be built against the optional TGeo2VecGeom +/// package; see GeometryManager::isVecGeomAvailable()). +enum class MatbudGeomBackend : int { ROOT = 0, + VECGEOM = 1 }; + /// Class for interfacing to the geometry; it also builds and manages the look-up tables for fast /// access to geometry and alignment information for sensitive alignable volumes: /// 1) the look-up table mapping unique volume ids to TGeoPNEntries. This allows to access @@ -69,7 +76,7 @@ class GeometryManager : public TObject static int getSensID(o2::detectors::DetID detid, int sensid) { /// compose combined detector+sensor ID for sensitive volumes - return (detid << sDetOffset) | (sensid & sSensorMask); + return detid <= o2::detectors::DetID::FOC ? ((detid << sDetOffset) | (sensid & sSensorMask)) : ((detid << sDetOffsetLarge) | (sensid & sSensorMaskLarge)); } /// Default destructor @@ -96,14 +103,18 @@ class GeometryManager : public TObject ClassDefNV(MatBudgetExt, 1); }; - static o2::base::MatBudget meanMaterialBudget(float x0, float y0, float z0, float x1, float y1, float z1); - static o2::base::MatBudget meanMaterialBudget(const math_utils::Point3D& start, const math_utils::Point3D& end) + /// Mean material budget between two points. Pass a navigator owned by the calling thread to + /// run lock-free from several threads; with nav = nullptr the shared navigator is used under + /// a mutex, as before. + static o2::base::MatBudget meanMaterialBudget(float x0, float y0, float z0, float x1, float y1, float z1, + TGeoNavigator* nav = nullptr); + static o2::base::MatBudget meanMaterialBudget(const math_utils::Point3D& start, const math_utils::Point3D& end, TGeoNavigator* nav = nullptr) { - return meanMaterialBudget(start.X(), start.Y(), start.Z(), end.X(), end.Y(), end.Z()); + return meanMaterialBudget(start.X(), start.Y(), start.Z(), end.X(), end.Y(), end.Z(), nav); } - static o2::base::MatBudget meanMaterialBudget(const math_utils::Point3D& start, const math_utils::Point3D& end) + static o2::base::MatBudget meanMaterialBudget(const math_utils::Point3D& start, const math_utils::Point3D& end, TGeoNavigator* nav = nullptr) { - return meanMaterialBudget(start.X(), start.Y(), start.Z(), end.X(), end.Y(), end.Z()); + return meanMaterialBudget(start.X(), start.Y(), start.Z(), end.X(), end.Y(), end.Z(), nav); } static MatBudgetExt meanMaterialBudgetExt(float x0, float y0, float z0, float x1, float y1, float z1); @@ -116,6 +127,17 @@ class GeometryManager : public TObject return meanMaterialBudgetExt(start.X(), start.Y(), start.Z(), end.X(), end.Y(), end.Z()); } + /// Whether this build of O2 was configured with the optional VecGeom material-budget + /// backend (i.e. TGeo2VecGeom was found at CMake configure time). +#ifdef O2_WITH_VECGEOM + static constexpr bool isVecGeomAvailable() { return true; } + /// Mean material budget between two points, using the VecGeom backend. On first call, + /// lazily converts the currently loaded TGeo geometry to VecGeom (once per process). + static o2::base::MatBudget vecGeomMaterialBudget(float x0, float y0, float z0, float x1, float y1, float z1); +#else + static constexpr bool isVecGeomAvailable() { return false; } +#endif + private: /// Default constructor GeometryManager() = default; @@ -135,8 +157,9 @@ class GeometryManager : public TObject private: /// sensitive volume identifier composed from (det_ID< #endif #include "GPUCommonDef.h" +#ifndef GPUCA_ALIGPUCODE +#include "DetectorsBase/GeometryManager.h" // for MatbudGeomBackend +#endif #include "FlatObject.h" #include "GPUCommonRtypes.h" #include "GPUCommonMath.h" #include "DetectorsBase/MatCell.h" +class TGeoNavigator; + namespace o2 { namespace base @@ -65,8 +70,8 @@ class MatLayerCyl : public o2::gpu::FlatObject void initSegmentation(float rMin, float rMax, float zHalfSpan, int nz, int nphi); void initSegmentation(float rMin, float rMax, float zHalfSpan, float dzMin, float drphiMin); - void populateFromTGeo(int ntrPerCell = 10); - void populateFromTGeo(int ip, int iz, int ntrPerCell); + void populateFromTGeo(int ntrPerCell = 10, MatbudGeomBackend backend = MatbudGeomBackend::ROOT); + void populateFromTGeo(int ip, int iz, int ntrPerCell, TGeoNavigator* nav = nullptr, MatbudGeomBackend backend = MatbudGeomBackend::ROOT); void print(bool data = false) const; #endif // !GPUCA_ALIGPUCODE @@ -107,7 +112,7 @@ class MatLayerCyl : public o2::gpu::FlatObject GPUd() int getZBinID(float z) const { int idz = int((z - getZMin()) * getDZInv()); // cannot be negative since before isZOutside is applied - return idz < getNZBins() ? idz : getNZBins() - 1; + return idz < getNZBins() ? (idz > 0 ? idz : 0) : getNZBins() - 1; } // lower boundary of Z slice diff --git a/Detectors/Base/include/DetectorsBase/MatLayerCylSet.h b/Detectors/Base/include/DetectorsBase/MatLayerCylSet.h index cba6e5cebcfc8..f2e8937ae8448 100644 --- a/Detectors/Base/include/DetectorsBase/MatLayerCylSet.h +++ b/Detectors/Base/include/DetectorsBase/MatLayerCylSet.h @@ -73,7 +73,12 @@ class MatLayerCylSet : public o2::gpu::FlatObject #ifndef GPUCA_ALIGPUCODE // this part is unvisible on GPU version void print(bool data = false) const; void addLayer(float rmin, float rmax, float zmax, float dz, float drphi); - void populateFromTGeo(int ntrPerCel = 10); + /// Populate the LUT from TGeo or VecGeom. nThreads > 1 fills cells in parallel (one + /// TGeoNavigator per thread for ROOT; VecGeom navigation is thread-safe on its own); + /// nThreads < 0 takes the count from NTHREADS_MATBUD. VECGEOM requires O2 built against + /// TGeo2VecGeom, see GeometryManager::isVecGeomAvailable(). + void populateFromTGeo(int ntrPerCel = 10, int nThreads = -1, MatbudGeomBackend backend = MatbudGeomBackend::ROOT); + static int getNThreadsFromEnv(); void optimizePhiSlices(float maxRelDiff = 0.05); void dumpToTree(const std::string& outName = "matbudTree.root") const; diff --git a/Detectors/Base/include/DetectorsBase/Propagator.h b/Detectors/Base/include/DetectorsBase/Propagator.h index 75b9446aebade..377094cc368b8 100644 --- a/Detectors/Base/include/DetectorsBase/Propagator.h +++ b/Detectors/Base/include/DetectorsBase/Propagator.h @@ -181,9 +181,9 @@ class PropagatorImpl return &instance; } static int initFieldFromGRP(const o2::parameters::GRPMagField* grp, bool verbose = false); - static int initFieldFromGRP(const o2::parameters::GRPObject* grp, bool verbose = false); static int initFieldFromGRP(const std::string grpFileName = "", bool verbose = false); + static int initFieldFromGRP(float currL3, float currDip, bool uniform, bool verbose = false); #endif GPUd() MatBudget getMatBudget(MatCorrType corrType, const o2::math_utils::Point3D& p0, const o2::math_utils::Point3D& p1) const; diff --git a/Detectors/Base/include/DetectorsBase/Ray.h b/Detectors/Base/include/DetectorsBase/Ray.h index a72208c41af0d..0b0c2f2904d27 100644 --- a/Detectors/Base/include/DetectorsBase/Ray.h +++ b/Detectors/Base/include/DetectorsBase/Ray.h @@ -79,7 +79,7 @@ class Ray GPUd() float getPhi(float t) const { - float p = o2::gpu::CAMath::ATan2(mP[1] + t * mD[1], mP[0] + t * mD[0]); + float p = o2::math_utils::fastATan2(mP[1] + t * mD[1], mP[0] + t * mD[0]); // instead of float p = o2::gpu::CAMath::ATan2(mP[1] + t * mD[1], mP[0] + t * mD[0]); o2::math_utils::bringTo02Pi(p); return p; } diff --git a/Detectors/Base/include/DetectorsBase/VMCSeederService.h b/Detectors/Base/include/DetectorsBase/VMCSeederService.h index 1669c73b39620..5f8f70f48840f 100644 --- a/Detectors/Base/include/DetectorsBase/VMCSeederService.h +++ b/Detectors/Base/include/DetectorsBase/VMCSeederService.h @@ -35,13 +35,17 @@ class VMCSeederService void setSeed() const; // will propagate seed to the VMC engines + /// how often a seed was propagated; lets callers detect a silent no-op + unsigned long long getSeedCount() const { return mSeedCount; } + typedef std::function SeederFcn; private: VMCSeederService(); void initSeederFunction(TVirtualMC const*); - SeederFcn mSeederFcn; // the just-in-time compiled function talking to the VMC engines + SeederFcn mSeederFcn; // the just-in-time compiled function talking to the VMC engines + mutable unsigned long long mSeedCount{0}; // number of setSeed() calls }; } // namespace base diff --git a/Detectors/Base/src/CADGeometryUtils.cxx b/Detectors/Base/src/CADGeometryUtils.cxx new file mode 100644 index 0000000000000..84c1890d844b5 --- /dev/null +++ b/Detectors/Base/src/CADGeometryUtils.cxx @@ -0,0 +1,202 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include "DetectorsBase/CADGeometryUtils.h" +#include "DetectorsBase/MaterialManager.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace o2::base +{ + +TGeoVolume* buildCADVolumeFromMacro(const std::string& macroFile, const std::string& instanceTag) +{ + if (macroFile.empty()) { + return nullptr; + } + auto expandedHookFileName = o2::utils::expandShellVarsInFileName(macroFile); + if (!std::filesystem::exists(expandedHookFileName)) { + LOG(error) << "External geometry macro " << expandedHookFileName << " does not exist"; + return nullptr; + } + + // We JIT the macro into a *unique* namespace per call. This is essential when several + // external geometries are present at the same time: every macro produced by + // O2_CADtoTGeo.py exports identically named symbols (build(), get_builder_hook_unchecked(), + // LoadFacets(), ...). Loading them all into the single global Cling scope would collide + // (the first definition wins and subsequent macros are silently ignored). By wrapping each + // macro body in its own namespace we keep the symbols separate. The preprocessor #include + // lines must stay at global scope, so we hoist them out of the namespace. + std::ifstream macroStream(expandedHookFileName, std::ios::in); + if (!macroStream.is_open()) { + LOG(error) << "Cannot open external geometry macro " << expandedHookFileName; + return nullptr; + } + std::string preamble; // #include (and other top-level preprocessor) lines -> global scope + std::string body; // everything else -> wrapped into a unique namespace + std::string line; + while (std::getline(macroStream, line)) { + auto firstNonSpace = line.find_first_not_of(" \t"); + if (firstNonSpace != std::string::npos && line[firstNonSpace] == '#') { + preamble += line + "\n"; + } else { + body += line + "\n"; + } + } + + // build a unique, valid C++ identifier for the namespace + static std::atomic instanceCounter{0}; + std::string ns = std::string("o2_cadgeom_") + instanceTag + "_" + std::to_string(instanceCounter++); + for (auto& c : ns) { + if (!std::isalnum(static_cast(c)) && c != '_') { + c = '_'; + } + } + + const std::string wrapped = preamble + "\nnamespace " + ns + " {\n" + body + "\n}\n"; + if (!gInterpreter->Declare(wrapped.c_str())) { + LOG(error) << "Failed to JIT external geometry macro " << expandedHookFileName; + return nullptr; + } + + // retrieve the builder hook from the unique namespace + const std::string globalName = "__" + ns + "_hook__"; + gROOT->ProcessLine(Form("std::function %s = %s::get_builder_hook_unchecked();", + globalName.c_str(), ns.c_str())); + auto global = gROOT->GetGlobal(globalName.c_str()); + if (!global) { + LOG(error) << "Could not retrieve geometry builder hook from macro " << expandedHookFileName; + return nullptr; + } + auto hook = *reinterpret_cast*>(global->GetAddress()); + LOG(info) << "CAD geometry hook initialized from file " << expandedHookFileName << " (namespace " << ns << ")"; + + auto top = hook(); + if (!top) { + LOG(error) << "CAD geometry macro " << expandedHookFileName << " did not return a top volume"; + } + return top; +} + +void remapCADMedia(TGeoVolume* top, const char* modulename) +{ + std::unordered_map medium_ptr_mapping; + std::unordered_set volumes_already_treated; + int counter = 1; + + // The transformer function + auto transform_media = [&](TGeoVolume* vol_) { + if (volumes_already_treated.find(vol_) != volumes_already_treated.end()) { + // this volume was already transformed + return; + } + volumes_already_treated.insert(vol_); + + if (dynamic_cast(vol_)) { + // do nothing for assemblies (they don't have a medium) + return; + } + + auto medium = vol_->GetMedium(); + if (!medium) { + return; + } + + auto iter = medium_ptr_mapping.find(medium); + if (iter != medium_ptr_mapping.end()) { + // This medium has already been transformed, so + // we just update the volume + vol_->SetMedium(iter->second); + return; + } else { + LOG(info) << "Transforming media with name " << medium->GetName() << " for volume " << vol_->GetName(); + + // we found a medium, not yet treated + auto curr_mat = medium->GetMaterial(); + auto& matmgr = o2::base::MaterialManager::Instance(); + + matmgr.Material(modulename, counter, curr_mat->GetName(), curr_mat->GetA(), curr_mat->GetZ(), curr_mat->GetDensity(), curr_mat->GetRadLen(), curr_mat->GetIntLen()); + // TGeo medium params are stored in a flat array with the following convention + // fParams[0] = isvol; + // fParams[1] = ifield; + // fParams[2] = fieldm; + // fParams[3] = tmaxfd; + // fParams[4] = stemax; + // fParams[5] = deemax; + // fParams[6] = epsil; + // fParams[7] = stmin; + const auto isvol = medium->GetParam(0); + const auto isxfld = medium->GetParam(1); + const auto sxmgmx = medium->GetParam(2); + const auto tmaxfd = medium->GetParam(3); + const auto stemax = medium->GetParam(4); + const auto deemax = medium->GetParam(5); + const auto epsil = medium->GetParam(6); + const auto stmin = medium->GetParam(7); + + matmgr.Medium(modulename, counter, medium->GetName(), counter, isvol, isxfld, sxmgmx, tmaxfd, stemax, deemax, epsil, stmin); + + // there will be new Material and Medium objects; fetch them + auto new_med = matmgr.getTGeoMedium(modulename, counter); + + // insert into cache + medium_ptr_mapping[medium] = new_med; + vol_->SetMedium(new_med); + counter++; + } + }; // end transformer lambda + + // a generic volume walker + std::function visit_volume; + visit_volume = [&](TGeoVolume* vol) -> void { + if (!vol) { + return; + } + + // call the transformer + transform_media(vol); + + // Recurse into daughters + const int nd = vol->GetNdaughters(); + for (int i = 0; i < nd; ++i) { + TGeoNode* node = vol->GetNode(i); + if (!node) { + continue; + } + TGeoVolume* child = node->GetVolume(); + if (!child) { + continue; + } + + visit_volume(child); + } + }; + + visit_volume(top); +} + +} // namespace o2::base diff --git a/Detectors/Base/src/DetectorsBaseLinkDef.h b/Detectors/Base/src/DetectorsBaseLinkDef.h index 8255c143ebb4a..2da1d5bdfad15 100644 --- a/Detectors/Base/src/DetectorsBaseLinkDef.h +++ b/Detectors/Base/src/DetectorsBaseLinkDef.h @@ -24,6 +24,7 @@ #pragma link C++ class o2::base::GeometryManager + ; #pragma link C++ class o2::base::GeometryManager::MatBudgetExt + ; +#pragma link C++ enum o2::base::MatbudGeomBackend; #pragma link C++ class o2::base::MaterialManager + ; #pragma link C++ class o2::MaterialManagerParam + ; #pragma link C++ class o2::GeometryManagerParam + ; diff --git a/Detectors/Base/src/GeometryManager.cxx b/Detectors/Base/src/GeometryManager.cxx index a067767752a69..225f21d8239a1 100644 --- a/Detectors/Base/src/GeometryManager.cxx +++ b/Detectors/Base/src/GeometryManager.cxx @@ -16,6 +16,7 @@ #include // for TIter #include #include // for TGeoHMatrix +#include // for TGeoNavigator #include // for TGeoNode #include // for TGeoPhysicalNode, TGeoPNEntry #include @@ -29,6 +30,22 @@ #include "CommonUtils/NameConf.h" #include "DetectorsBase/Aligner.h" +#ifdef O2_WITH_VECGEOM +#include "TGeo2VecGeom/RootGeoManager.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + using namespace o2::detectors; using namespace o2::base; @@ -398,7 +415,8 @@ GeometryManager::MatBudgetExt GeometryManager::meanMaterialBudgetExt(float x0, f } //_____________________________________________________________________________________ -o2::base::MatBudget GeometryManager::meanMaterialBudget(float x0, float y0, float z0, float x1, float y1, float z1) +o2::base::MatBudget GeometryManager::meanMaterialBudget(float x0, float y0, float z0, float x1, float y1, float z1, + TGeoNavigator* nav) { // // Calculate mean material budget and material properties between @@ -414,6 +432,8 @@ o2::base::MatBudget GeometryManager::meanMaterialBudget(float x0, float y0, floa // // Ported to O2: ruben.shahoyan@cern.ch // + // Multi-threaded execution: pass a navigator owned by the calling thread. + // double length, startD[3] = {x0, y0, z0}; double dir[3] = {x1 - x0, y1 - y0, z1 - z0}; @@ -425,9 +445,17 @@ o2::base::MatBudget GeometryManager::meanMaterialBudget(float x0, float y0, floa for (int i = 3; i--;) { dir[i] *= invlen; } - std::lock_guard guard(sTGMutex); + // A caller that passes its own navigator owns it exclusively, so no locking is needed. A caller + // that passes none shares gGeoManager's current navigator and must still serialize. Deciding + // this from the argument keeps the choice local: it does not depend on -- and cannot be broken + // by -- process-global state such as TGeoManager::GetMaxThreads(). + std::unique_lock guard(sTGMutex, std::defer_lock); + if (!nav) { + guard.lock(); + nav = gGeoManager->GetCurrentNavigator(); + } // Initialize start point and direction - TGeoNode* currentnode = gGeoManager->InitTrack(startD, dir); + TGeoNode* currentnode = nav->InitTrack(startD, dir); if (!currentnode) { LOG(error) << "start point out of geometry: " << x0 << ':' << y0 << ':' << z0; return o2::base::MatBudget(); // return empty struct @@ -439,11 +467,11 @@ o2::base::MatBudget GeometryManager::meanMaterialBudget(float x0, float y0, floa // Locate next boundary within length without computing safety. // Propagate either with length (if no boundary found) or just cross boundary - gGeoManager->FindNextBoundaryAndStep(length, kFALSE); + nav->FindNextBoundaryAndStep(length, kFALSE); Double_t stepTot = 0.0; // Step made - Double_t step = gGeoManager->GetStep(); + Double_t step = nav->GetStep(); // If no boundary within proposed length, return current step data - if (!gGeoManager->IsOnBoundary()) { + if (!nav->IsOnBoundary()) { budStep.meanX2X0 = budStep.length / budStep.meanX2X0; return o2::base::MatBudget(budStep); } @@ -458,7 +486,7 @@ o2::base::MatBudget GeometryManager::meanMaterialBudget(float x0, float y0, floa if (nzero > 3) { // This means navigation has problems on one boundary // Try to cross by making a small step - const double* curPos = gGeoManager->GetCurrentPoint(); + const double* curPos = nav->GetCurrentPoint(); LOG(warning) << "Cannot cross boundary at (" << curPos[0] << ',' << curPos[1] << ',' << curPos[2] << ')'; budTotal.meanRho /= stepTot; budTotal.length = stepTot; @@ -472,14 +500,14 @@ o2::base::MatBudget GeometryManager::meanMaterialBudget(float x0, float y0, floa if (step >= length) { break; } - currentnode = gGeoManager->GetCurrentNode(); + currentnode = nav->GetCurrentNode(); if (!currentnode) { break; } length -= step; accountMaterial(currentnode->GetVolume()->GetMedium()->GetMaterial(), budStep); - gGeoManager->FindNextBoundaryAndStep(length, kFALSE); - step = gGeoManager->GetStep(); + nav->FindNextBoundaryAndStep(length, kFALSE); + step = nav->GetStep(); } budTotal.meanRho /= stepTot; budTotal.length = stepTot; @@ -524,3 +552,129 @@ void GeometryManager::loadGeometry(std::string_view simPrefix, bool applyMisalig applyMisalignent(applyMisalignment); } } + +#ifdef O2_WITH_VECGEOM + +namespace +{ +/// Converts the currently loaded TGeo geometry to VecGeom and sets up navigators, once per +/// process, the first time the VecGeom backend is requested. Not part of loadGeometry(), +/// which every job calls regardless of whether it ever uses the VecGeom backend. +void ensureVecGeomWorldBuilt() +{ + static std::once_flag onceFlag; + std::call_once(onceFlag, []() { + if (!gGeoManager) { + LOG(fatal) << "Cannot build VecGeom geometry: no TGeo geometry loaded (call GeometryManager::loadGeometry() first)"; + } + // Translate geometry and material pointers, then build acceleration structures. + tgeo2vecgeom::RootGeoManager::Instance().SetMaterialConversionHook([](TGeoMaterial const* m) { return (void*)m; }); + tgeo2vecgeom::RootGeoManager::Instance().SetFlattenAssemblies(true); + tgeo2vecgeom::RootGeoManager::Instance().LoadRootGeometry(); + + // Acceleration structures must be built before the navigators/locators reference them. + vecgeom::ABBoxManager::Instance().InitABBoxesForCompleteGeometry(); + // Builds a BVH per logical volume from the ABBoxes computed above. + vecgeom::BVHManager::Init(); + + // For each logical volume, set both a navigator (used for ComputeStep) and a matched + // level locator (used for point relocation after a boundary crossing via GlobalLocator); + // volumes with very few daughters are cheaper to brute-force than to accelerate. + for (auto& lvol : vecgeom::GeoManager::Instance().GetLogicalVolumesMap()) { + auto* vol = lvol.second; + if (vol->GetDaughtersp()->size() <= 2) { + vol->SetNavigator(vecgeom::NewSimpleNavigator<>::Instance()); + vol->SetLevelLocator(vecgeom::SimpleLevelLocator::GetInstance()); + } else { + vol->SetNavigator(vecgeom::BVHNavigator<>::Instance()); + vol->SetLevelLocator(vecgeom::BVHLevelLocator::GetInstance()); + } + } + }); +} +} // namespace + +//_____________________________________________________________________________________ +o2::base::MatBudget GeometryManager::vecGeomMaterialBudget(float x0, float y0, float z0, float x1, float y1, float z1) +{ + // Mean material budget between "0" and "1" via VecGeom's BVH-accelerated ray/boundary + // intersection, instead of TGeo. + ensureVecGeomWorldBuilt(); + + using Vector3D = vecgeom::Vector3D; + + double length, start[3] = {x0, y0, z0}; + double dir[3] = {x1 - x0, y1 - y0, z1 - z0}; + if ((length = dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]) < TGeoShape::Tolerance() * TGeoShape::Tolerance()) { + return o2::base::MatBudget(); // return empty struct + } + length = std::sqrt(length); + double invlen = 1. / length; + for (int i = 3; i--;) { + dir[i] *= invlen; + } + + thread_local static vecgeom::NavigationState* newnavstate = vecgeom::NavigationState::MakeInstance(vecgeom::GeoManager::Instance().getMaxDepth()); + thread_local static vecgeom::NavigationState* currnavstate = vecgeom::NavigationState::MakeInstance(vecgeom::GeoManager::Instance().getMaxDepth()); + thread_local static vecgeom::NavigationState* startCache = vecgeom::NavigationState::MakeInstance(vecgeom::GeoManager::Instance().getMaxDepth()); + thread_local static bool startCacheValid = false; + + Vector3D currPoint(x0, y0, z0); + Vector3D dirr(dir[0], dir[1], dir[2]); + constexpr double kPush = 1.E-6; // mimick the nudging of TGeo's FindNextBoundaryAndStep + auto world = vecgeom::GeoManager::Instance().GetWorld(); + o2::base::MatBudget budTot, budStep; + budStep.length = length; + + // Locate the starting volume, reusing the path from the previous call when still valid. + if (startCacheValid && !startCache->IsOutside()) { + startCache->CopyTo(currnavstate); + vecgeom::Transformation3D m; + currnavstate->TopMatrix(m); + vecgeom::GlobalLocator::RelocatePointFromPath(m.Transform(currPoint), *currnavstate); + } else { + currnavstate->Clear(); + vecgeom::GlobalLocator::LocateGlobalPoint(world, currPoint, *currnavstate, true); + } + if (currnavstate->IsOutside() || currnavstate->Top() == nullptr) { + LOG(error) << "start point out of geometry: " << x0 << ':' << y0 << ':' << z0; + startCacheValid = false; + return o2::base::MatBudget(); + } + currnavstate->CopyTo(startCache); + startCacheValid = true; + + double stepTot = 0.; + double remainingDist = length; + Int_t nzero = 0; + while (remainingDist > 1.E-10) { + auto* lvol = currnavstate->Top()->GetLogicalVolume(); + accountMaterial(static_cast(lvol->GetMaterialPtr()), budStep); + vecgeom::VNavigator const* navigator = lvol->GetNavigator(); + double step = static_cast(navigator->ComputeStepAndPropagatedState(currPoint, dirr, remainingDist, *currnavstate, *newnavstate)); + if (step < 2.E-10) { + nzero++; + } else { + nzero = 0; + } + if (nzero > 3) { + // This means navigation has problems on one boundary + LOG(warning) << "Cannot cross boundary at (" << currPoint[0] << ',' << currPoint[1] << ',' << currPoint[2] << ')'; + budTot.meanRho /= stepTot; + budTot.length = stepTot; + return o2::base::MatBudget(budTot); + } + + remainingDist -= step; + stepTot += step; + budTot.meanRho += step * budStep.meanRho; + budTot.meanX2X0 += step / budStep.meanX2X0; + currPoint = currPoint + (step + kPush) * dirr; + std::swap(currnavstate, newnavstate); + } + budTot.meanRho /= stepTot; + budTot.length = stepTot; + return o2::base::MatBudget(budTot); +} + +#endif // O2_WITH_VECGEOM diff --git a/Detectors/Base/src/MatLayerCyl.cxx b/Detectors/Base/src/MatLayerCyl.cxx index 2efe60235b895..c35293d07c10e 100644 --- a/Detectors/Base/src/MatLayerCyl.cxx +++ b/Detectors/Base/src/MatLayerCyl.cxx @@ -109,7 +109,7 @@ void MatLayerCyl::initSegmentation(float rMin, float rMax, float zHalfSpan, int } //________________________________________________________________________________ -void MatLayerCyl::populateFromTGeo(int ntrPerCell) +void MatLayerCyl::populateFromTGeo(int ntrPerCell, MatbudGeomBackend backend) { /// populate layer with info extracted from TGeometry, using ntrPerCell test tracks per cell assert(mConstructionMask != Constructed); @@ -117,13 +117,13 @@ void MatLayerCyl::populateFromTGeo(int ntrPerCell) ntrPerCell = ntrPerCell > 1 ? ntrPerCell : 1; for (int iz = getNZBins(); iz--;) { for (int ip = getNPhiBins(); ip--;) { - populateFromTGeo(ip, iz, ntrPerCell); + populateFromTGeo(ip, iz, ntrPerCell, nullptr, backend); } } } //________________________________________________________________________________ -void MatLayerCyl::populateFromTGeo(int ip, int iz, int ntrPerCell) +void MatLayerCyl::populateFromTGeo(int ip, int iz, int ntrPerCell, TGeoNavigator* nav, MatbudGeomBackend backend) { /// populate cell with info extracted from TGeometry, using ntrPerCell test tracks per cell @@ -136,7 +136,16 @@ void MatLayerCyl::populateFromTGeo(int ip, int iz, int ntrPerCell) float dzt = zs > 0.f ? 0.25 * dz : -0.25 * dz; // to avoid 90 degree polar angle for (int isp = ntrPerCell; isp--;) { o2::math_utils::sincos(phmn + (isp + 0.5) * getDPhi() / ntrPerCell, sn, cs); - auto bud = o2::base::GeometryManager::meanMaterialBudget(rMin * cs, rMin * sn, zs - dzt, rMax * cs, rMax * sn, zs + dzt); + o2::base::MatBudget bud; + if (backend == MatbudGeomBackend::ROOT) { + bud = o2::base::GeometryManager::meanMaterialBudget(rMin * cs, rMin * sn, zs - dzt, rMax * cs, rMax * sn, zs + dzt, nav); + } else { +#ifdef O2_WITH_VECGEOM + bud = o2::base::GeometryManager::vecGeomMaterialBudget(rMin * cs, rMin * sn, zs - dzt, rMax * cs, rMax * sn, zs + dzt); +#else + LOG(fatal) << "MatbudGeomBackend::VECGEOM requested but O2 was built without VecGeom support (TGeo2VecGeom not found at configure time)"; +#endif + } if (bud.length > 0.) { meanRho += bud.length * bud.meanRho; meanX2X0 += bud.meanX2X0; // we store actually not X2X0 but 1./X0 diff --git a/Detectors/Base/src/MatLayerCylSet.cxx b/Detectors/Base/src/MatLayerCylSet.cxx index c390c8d617326..b65a7472fbcf6 100644 --- a/Detectors/Base/src/MatLayerCylSet.cxx +++ b/Detectors/Base/src/MatLayerCylSet.cxx @@ -17,7 +17,16 @@ #ifndef GPUCA_ALIGPUCODE // this part is unvisible on GPU version #include "GPUCommonLogger.h" #include +#include #include "CommonUtils/TreeStreamRedirector.h" +#include +#include +#include +#include +#include +#include +#include +#include //#define _DBG_LOC_ // for local debugging only #endif // !GPUCA_ALIGPUCODE @@ -69,11 +78,32 @@ void MatLayerCylSet::addLayer(float rmin, float rmax, float zmax, float dz, floa } //________________________________________________________________________________ -void MatLayerCylSet::populateFromTGeo(int ntrPerCell) +int MatLayerCylSet::getNThreadsFromEnv() { - ///< populate layers, using ntrPerCell test tracks per cell + ///< number of threads requested via NTHREADS_MATBUD, or 1 if unset/invalid + const char* env = std::getenv("NTHREADS_MATBUD"); + if (!env) { + return 1; + } + int n = std::atoi(env); + if (n < 1) { + LOG(warning) << "Ignoring invalid NTHREADS_MATBUD=" << env; + return 1; + } + return n; +} + +//________________________________________________________________________________ +void MatLayerCylSet::populateFromTGeo(int ntrPerCell, int nThreads, MatbudGeomBackend backend) +{ + ///< populate layers, using ntrPerCell test tracks per cell. + ///< nThreads < 0 takes the number of threads from the NTHREADS_MATBUD environment variable. assert(mConstructionMask == InProgress); + if (backend == MatbudGeomBackend::VECGEOM && !GeometryManager::isVecGeomAvailable()) { + LOG(fatal) << "MatbudGeomBackend::VECGEOM requested but O2 was built without VecGeom support (TGeo2VecGeom not found at configure time)"; + } + int nlr = getNLayers(); if (!nlr) { LOG(error) << "The LUT is not yet initialized"; @@ -83,12 +113,115 @@ void MatLayerCylSet::populateFromTGeo(int ntrPerCell) LOG(error) << "The LUT is already populated"; return; } + + if (nThreads < 0) { + nThreads = getNThreadsFromEnv(); + } + + using Clock = std::chrono::steady_clock; + auto seconds = [](Clock::time_point a, Clock::time_point b) { + return std::chrono::duration(b - a).count(); + }; + + if (nThreads <= 1) { + for (int i = 0; i < nlr; i++) { + LOG(info) << "Populating with " << ntrPerCell << " trials Lr " << i; + get()->mLayers[i].print(); + } + const auto tSetupStart = Clock::now(); +#ifdef O2_WITH_VECGEOM + if (backend == MatbudGeomBackend::VECGEOM) { + // Trigger the lazy VecGeom world build/BVH init here so it counts as "setup" below, + // not as fill time for whichever cell happens first. + GeometryManager::vecGeomMaterialBudget(0.f, 0.f, 0.f, 0.f, 0.f, 1.f); + } +#endif + const auto tFillStart = Clock::now(); + for (int i = 0; i < nlr; i++) { + get()->mLayers[i].populateFromTGeo(ntrPerCell, backend); + } + const auto tFillEnd = Clock::now(); + finalizeStructures(); + LOG(info) << "LUT fill: 1 thread, setup " << seconds(tSetupStart, tFillStart) + << " s, cells " << seconds(tFillStart, tFillEnd) << " s"; + return; + } + + // Cells of all layers form one flat index range so that the load is balanced across + // threads even though layers differ a lot in cell count. layerOffsets[i] is the first + // flat index of layer i; a binary search maps a flat index back to (layer, iz, iphi). + std::vector layerOffsets(nlr + 1, 0); for (int i = 0; i < nlr; i++) { - printf("Populating with %d trials Lr %3d ", ntrPerCell, i); + LOG(info) << "Queuing " << ntrPerCell << " trials Lr " << i; get()->mLayers[i].print(); - get()->mLayers[i].populateFromTGeo(ntrPerCell); + const auto& lr = get()->mLayers[i]; + layerOffsets[i + 1] = layerOffsets[i] + size_t(lr.getNZBins()) * lr.getNPhiBins(); } + const size_t totalCells = layerOffsets[nlr]; + + const auto tSetupStart = Clock::now(); + + auto fillRange = [this, ntrPerCell, backend, &layerOffsets](const tbb::blocked_range& range, TGeoNavigator* nav) { + for (size_t idx = range.begin(); idx != range.end(); ++idx) { + auto it = std::upper_bound(layerOffsets.begin(), layerOffsets.end(), idx); + const int layerIdx = int(std::distance(layerOffsets.begin(), it)) - 1; + const size_t cellInLayer = idx - layerOffsets[layerIdx]; + auto& layer = this->get()->mLayers[layerIdx]; + const int nphi = layer.getNPhiBins(); + layer.populateFromTGeo(int(cellInLayer % nphi), int(cellInLayer / nphi), ntrPerCell, nav, backend); + } + }; + + Clock::time_point tFillStart, tFillEnd; + if (backend == MatbudGeomBackend::ROOT) { + // TGeo has to be told that several threads will navigate it, and each thread needs its own + // navigator. SetMaxThreads() is one-way -- TGeoManager has no API to return to + // single-threaded mode -- so we do not pretend to restore it; that is harmless because + // meanMaterialBudget() decides whether to lock from its own argument, not from this global. + // The navigators we book are ours, though, so those we do give back. + gGeoManager->SetMaxThreads(nThreads); + + tbb::enumerable_thread_specific threadNavigators( + []() { return gGeoManager->AddNavigator(); }); + + tFillStart = Clock::now(); + { + tbb::global_control threadControl(tbb::global_control::max_allowed_parallelism, nThreads); + tbb::parallel_for(tbb::blocked_range(0, totalCells), + [&fillRange, &threadNavigators](const tbb::blocked_range& range) { + fillRange(range, threadNavigators.local()); + }); + } + tFillEnd = Clock::now(); + + for (TGeoNavigator* nav : threadNavigators) { + gGeoManager->RemoveNavigator(nav); + } + } else { + // VecGeom navigation needs no per-thread navigator bookkeeping. Trigger the lazy world + // build/BVH init before tFillStart so it counts as "setup", not fill time. +#ifdef O2_WITH_VECGEOM + GeometryManager::vecGeomMaterialBudget(0.f, 0.f, 0.f, 0.f, 0.f, 1.f); +#endif + tFillStart = Clock::now(); + { + tbb::global_control threadControl(tbb::global_control::max_allowed_parallelism, nThreads); + tbb::parallel_for(tbb::blocked_range(0, totalCells), + [&fillRange](const tbb::blocked_range& range) { + fillRange(range, nullptr); + }); + } + tFillEnd = Clock::now(); + } + finalizeStructures(); + const auto tEnd = Clock::now(); + + // Reported separately because only the middle term scales: the setup walks every volume + // in the geometry (TGeoManager::SetMaxThreads) and the teardown is serial by nature. + LOG(info) << "LUT fill: " << nThreads << " threads, setup " << seconds(tSetupStart, tFillStart) + << " s, cells " << seconds(tFillStart, tFillEnd) + << " s, finalize " << seconds(tFillEnd, tEnd) << " s"; } //________________________________________________________________________________ @@ -348,6 +481,12 @@ GPUd() MatBudget MatLayerCylSet::getMatBudget(float x0, float y0, float z0, floa if (tEndPhi == Ray::InvalidT) { break; // ray parallel to radial line, abandon check for phi bin change } + const auto tMarginPhi = 1.e-6f + 1.e-5f * (cross1 - cross2); + // if (!(tEndPhi >= cross2 - tMarginPhi) | !(tEndPhi <= cross1 + tMarginPhi)) { // use non-short-circuit | to reject eventual NANs + if (tEndPhi < cross2 - tMarginPhi || tEndPhi > cross1 + tMarginPhi) { + tEndPhi = cross2; + checkMorePhi = false; + } } auto zID = lr.getZBinID(ray.getZ(tStartPhi)); auto zIDLast = lr.getZBinID(ray.getZ(tEndPhi)); diff --git a/Detectors/Base/src/Propagator.cxx b/Detectors/Base/src/Propagator.cxx index 208b9bf138688..a465bbc312c77 100644 --- a/Detectors/Base/src/Propagator.cxx +++ b/Detectors/Base/src/Propagator.cxx @@ -84,7 +84,6 @@ int PropagatorImpl::initFieldFromGRP(const std::string grpFileName, boo if (verbose) { grp->print(); } - return initFieldFromGRP(grp); } @@ -92,27 +91,7 @@ int PropagatorImpl::initFieldFromGRP(const std::string grpFileName, boo template int PropagatorImpl::initFieldFromGRP(const o2::parameters::GRPObject* grp, bool verbose) { - /// init mag field from GRP data and attach it to TGeoGlobalMagField - - if (TGeoGlobalMagField::Instance()->IsLocked()) { - if (TGeoGlobalMagField::Instance()->GetField()->TestBit(o2::field::MagneticField::kOverrideGRP)) { - LOG(warning) << "ExpertMode!!! GRP information will be ignored"; - LOG(warning) << "ExpertMode!!! Running with the externally locked B field"; - return 0; - } else { - LOG(info) << "Destroying existing B field instance"; - delete TGeoGlobalMagField::Instance(); - } - } - auto fld = o2::field::MagneticField::createFieldMap(grp->getL3Current(), grp->getDipoleCurrent(), o2::field::MagneticField::kConvLHC, grp->getFieldUniformity()); - TGeoGlobalMagField::Instance()->SetField(fld); - TGeoGlobalMagField::Instance()->Lock(); - if (verbose) { - LOG(info) << "Running with the B field constructed out of GRP"; - LOG(info) << "Access field via TGeoGlobalMagField::Instance()->Field(xyz,bxyz) or via"; - LOG(info) << "auto o2field = static_cast( TGeoGlobalMagField::Instance()->GetField() )"; - } - return 0; + return initFieldFromGRP(grp->getL3Current(), grp->getDipoleCurrent(), grp->getFieldUniformity(), verbose); } //____________________________________________________________ @@ -120,25 +99,53 @@ template int PropagatorImpl::initFieldFromGRP(const o2::parameters::GRPMagField* grp, bool verbose) { /// init mag field from GRP data and attach it to TGeoGlobalMagField + return initFieldFromGRP(grp->getL3Current(), grp->getDipoleCurrent(), grp->getFieldUniformity(), verbose); +} - if (TGeoGlobalMagField::Instance()->IsLocked()) { - if (TGeoGlobalMagField::Instance()->GetField()->TestBit(o2::field::MagneticField::kOverrideGRP)) { - LOG(warning) << "ExpertMode!!! GRP information will be ignored"; - LOG(warning) << "ExpertMode!!! Running with the externally locked B field"; - return 0; +//____________________________________________________________ +template +int PropagatorImpl::initFieldFromGRP(float currL3, float currDip, bool uniform, bool verbose) +{ + /// init mag field from GRP data and attach it to TGeoGlobalMagField or rescale already updated field + auto fldGlo = static_cast(TGeoGlobalMagField::Instance()->GetField()); + if (fldGlo) { // global field object was already initialized, reuse it if it is locked (as it normally should be) + float _currL3(currL3), _currDip(currDip); + auto newFieldType = fldGlo->getFieldMapScale(_currL3, _currDip, uniform); + bool sameFieldType = newFieldType == fldGlo->getMapType(); + if (!sameFieldType) { + LOGP(warn, "Existing B-field type {} cannot be rescaled to type {} requested by the GRP", int(fldGlo->getMapType()), int(newFieldType)); + } + if (TGeoGlobalMagField::Instance()->IsLocked() && sameFieldType) { + if (Instance()->mField && Instance()->mField != fldGlo) { // just make sure that cached field is the same as the global one + std::string name{"PropagatorF"}; + if constexpr (std::is_same_v) { + std::string name{"PropagatorD"}; + } + LOGP(fatal, "Magnetic field pointer cached in the {} instance differs from the gloabal field pointer", name); + } + if (verbose) { + LOGP(info, "Rescaling magnetic field to currents L3: {}, Dipole: {}, UniformityFlag: {}", currL3, currDip, uniform); + } + fldGlo->rescaleField(currL3, currDip, uniform); } else { - LOG(info) << "Destroying existing B field instance"; + LOGP(warn, "Destroying existing B field instance. This may invalidate field pointer cached in other objects"); delete TGeoGlobalMagField::Instance(); + Instance()->mField = nullptr; + Instance()->mFieldFast = nullptr; + fldGlo = nullptr; } } - auto fld = o2::field::MagneticField::createFieldMap(grp->getL3Current(), grp->getDipoleCurrent(), o2::field::MagneticField::kConvLHC, grp->getFieldUniformity()); - TGeoGlobalMagField::Instance()->SetField(fld); - TGeoGlobalMagField::Instance()->Lock(); - if (verbose) { - LOG(info) << "Running with the B field constructed out of GRP"; - LOG(info) << "Access field via TGeoGlobalMagField::Instance()->Field(xyz,bxyz) or via"; - LOG(info) << "auto o2field = static_cast( TGeoGlobalMagField::Instance()->GetField() )"; + if (!fldGlo) { + fldGlo = o2::field::MagneticField::createFieldMap(currL3, currDip, o2::field::MagneticField::kConvLHC, uniform); + TGeoGlobalMagField::Instance()->SetField(fldGlo); + TGeoGlobalMagField::Instance()->Lock(); + if (verbose) { + LOG(info) << "Running with the B field constructed out of GRP"; + LOG(info) << "Access field via TGeoGlobalMagField::Instance()->Field(xyz,bxyz) or via"; + LOG(info) << "auto o2field = static_cast( TGeoGlobalMagField::Instance()->GetField() )"; + } } + Instance()->updateField(); return 0; } diff --git a/Detectors/Base/src/SimFieldUtils.cxx b/Detectors/Base/src/SimFieldUtils.cxx index 9673e39bf1b07..ba828c0c2ac25 100644 --- a/Detectors/Base/src/SimFieldUtils.cxx +++ b/Detectors/Base/src/SimFieldUtils.cxx @@ -33,7 +33,7 @@ FairField* const SimFieldUtils::createMagField() auto& ccdb = o2::ccdb::BasicCCDBManager::instance(); auto grpmagfield = ccdb.get("GLO/Config/GRPMagField"); // TODO: clarify if we need to pass other params such as beam energy/type etc. - field = o2::field::MagneticField::createFieldMap(grpmagfield->getL3Current(), grpmagfield->getDipoleCurrent(), grpmagfield->getFieldUniformity()); + field = o2::field::MagneticField::createFieldMap(grpmagfield->getL3Current(), grpmagfield->getDipoleCurrent(), o2::field::MagneticField::kConvLHC, grpmagfield->getFieldUniformity()); } // b) using the given values on the command line else { diff --git a/Detectors/Base/src/VMCSeederService.cxx b/Detectors/Base/src/VMCSeederService.cxx index 5bf4e1ed5641b..8fc36d9074fab 100644 --- a/Detectors/Base/src/VMCSeederService.cxx +++ b/Detectors/Base/src/VMCSeederService.cxx @@ -50,4 +50,5 @@ void VMCSeederService::setSeed() const // This is ok since in any case gRandom->SetSeed(seed); gRandom->GetSeed() != seed; gRandom->Rndm(); mSeederFcn(); + ++mSeedCount; } diff --git a/Detectors/Base/test/README.md b/Detectors/Base/test/README.md index f5f9fd4c04b29..ca7fea02949ba 100644 --- a/Detectors/Base/test/README.md +++ b/Detectors/Base/test/README.md @@ -13,6 +13,25 @@ root -b -q O2/Detectors/Base/test/buildMatBudLUT.C+ The generation is quite time consuming (may take ~30 min). +It can be filled in parallel, one `TGeoNavigator` per thread, by passing a thread count as the +7th argument of `buildMatBudLUT` or by setting the environment variable: +``` +export NTHREADS_MATBUD=16 +``` +The result does not depend on the number of threads. Scaling beyond a few threads needs +ROOT >= v6-36-10-alice3, which removes the per-query thread-id lookup and the false sharing +between the per-thread scratch buffers of TGeo shapes; with older ROOT the parallel path is +still correct, just slower. + +An alternative VecGeom geometry backend can be selected via the 8th argument (`"ROOT"` +or `"VECGEOM"`), e.g. +``` +root -b -q 'O2/Detectors/Base/test/buildMatBudLUT.C(60, -1, "matbud.root", "o2sim", "", 16, "VECGEOM")' +``` +This requires O2 to have been built against the optional `TGeo2VecGeom` package +(`o2::base::GeometryManager::isVecGeomAvailable()`); it is otherwise a build-time no-op that +does not affect the default ROOT/TGeo path in any way. + The optimized LUT will be stored in the matbud.root file. Load it as: diff --git a/Detectors/Base/test/buildMatBudLUT.C b/Detectors/Base/test/buildMatBudLUT.C index 860fcbd5da940..2b371b90effa3 100644 --- a/Detectors/Base/test/buildMatBudLUT.C +++ b/Detectors/Base/test/buildMatBudLUT.C @@ -23,11 +23,18 @@ #include #endif +using MatbudGeomBackend = o2::base::MatbudGeomBackend; + o2::base::MatLayerCylSet mbLUT; bool testMBLUT(const std::string& lutFile = "matbud.root"); +MatbudGeomBackend parseBackend(const std::string& s); -bool buildMatBudLUT(int nTst = 60, int maxLr = -1, const std::string& outFile = "matbud.root", const std::string& geomName = "o2sim_geometry-aligned.root"); +/// Build the material budget LUT. nThreads < 0 takes the thread count from NTHREADS_MATBUD. +/// geomBackend is "ROOT" (default) or "VECGEOM" (requires O2 built against TGeo2VecGeom). +bool buildMatBudLUT(int nTst = 60, int maxLr = -1, const std::string& outFile = "matbud.root", + const std::string& geomNamePrefix = "o2sim", const std::string& opts = "", + int nThreads = -1, const std::string& geomBackend = "ROOT"); struct LrData { float rMin = 0.f; @@ -42,8 +49,10 @@ struct LrData { std::vector lrData; void configLayers(); -bool buildMatBudLUT(int nTst, int maxLr, const std::string& outFile, const std::string& geomNamePrefix, const std::string& opts) +bool buildMatBudLUT(int nTst, int maxLr, const std::string& outFile, const std::string& geomNamePrefix, + const std::string& opts, int nThreads, const std::string& geomBackend) { + MatbudGeomBackend backend = parseBackend(geomBackend); auto geomName = o2::base::NameConf::getGeomFileName(geomNamePrefix); if (gSystem->AccessPathName(geomName.c_str())) { // if needed, create geometry std::cout << geomName << " does not exist. Will create it on the fly\n"; @@ -67,7 +76,7 @@ bool buildMatBudLUT(int nTst, int maxLr, const std::string& outFile, const std:: } TStopwatch sw; - mbLUT.populateFromTGeo(nTst); + mbLUT.populateFromTGeo(nTst, nThreads, backend); mbLUT.optimizePhiSlices(); // move to populateFromTGeo mbLUT.flatten(); // move to populateFromTGeo @@ -397,3 +406,19 @@ void configLayers() lrData.emplace_back(LrData(lrData.back().rMax, lrData.back().rMax + drStep, zSpanH, zBin, rphiBin)); } while (lrData.back().rMax < 500); } + +//_______________________________________________________________________ +MatbudGeomBackend parseBackend(const std::string& s) +{ + if (s == "ROOT") { + return MatbudGeomBackend::ROOT; + } + if (s == "VECGEOM") { + if (!o2::base::GeometryManager::isVecGeomAvailable()) { + LOG(fatal) << "geomBackend=VECGEOM requested but O2 was built without VecGeom support (TGeo2VecGeom not found at configure time)"; + } + return MatbudGeomBackend::VECGEOM; + } + LOG(fatal) << "Unknown geomBackend '" << s << "', expected ROOT or VECGEOM"; + return MatbudGeomBackend::ROOT; +} diff --git a/Detectors/Base/test/compareMatBudLUT.C b/Detectors/Base/test/compareMatBudLUT.C new file mode 100644 index 0000000000000..a58695d05d22c --- /dev/null +++ b/Detectors/Base/test/compareMatBudLUT.C @@ -0,0 +1,153 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file compareMatBudLUT.C +/// \brief Compare two material budget LUTs cell by cell +/// +/// Used to check that filling the LUT in parallel gives the same result as filling it serially, +/// or that the VecGeom and ROOT geometry backends agree within a given tolerance: +/// +/// root -b -q 'compareMatBudLUT.C("matbud_serial.root","matbud_parallel.root")' +/// root -b -q 'compareMatBudLUT.C("matbud_ROOT.root","matbud_VECGEOM.root", 0.01, 20, "sweep.csv")' + +#if !defined(__CLING__) || defined(__ROOTCLING__) +#include "DetectorsBase/MatLayerCylSet.h" +#include "GPUCommonLogger.h" +#include +#include +#include +#include +#include +#endif + +namespace +{ +struct CellDiff { + int layer, iz, ip; + float rhoA, rhoB, x2x0A, x2x0B; + double rRho, rX; + double score() const { return std::max(rRho, rX); } +}; + +struct LayerStat { + float rMin = 0.f, rMax = 0.f; + size_t nBad = 0; + double maxRelRho = 0., maxRelX2X0 = 0.; +}; +} // namespace + +/// Returns true if the two LUTs agree everywhere within tol (relative). +/// nWorst: number of worst-offending cells to print, ranked by max(relRho, relX2X0) over the +/// whole comparison, not just the first ones found in scan order. +/// csvSummary: if non-empty, append one summary row to this CSV file (header written once). +bool compareMatBudLUT(const std::string& fileA = "matbud_serial.root", + const std::string& fileB = "matbud_parallel.root", + float tol = 0.f, + int nWorst = 10, + const std::string& csvSummary = "") +{ + auto* lutA = o2::base::MatLayerCylSet::loadFromFile(fileA); + auto* lutB = o2::base::MatLayerCylSet::loadFromFile(fileB); + if (!lutA) { + LOG(error) << "Failed to load LUT from " << fileA; + return false; + } + if (!lutB) { + LOG(error) << "Failed to load LUT from " << fileB; + return false; + } + + if (lutA->getNLayers() != lutB->getNLayers()) { + LOG(error) << "Layer count differs: " << lutA->getNLayers() << " vs " << lutB->getNLayers(); + return false; + } + + size_t nCells = 0, nBad = 0; + double maxRelRho = 0., maxRelX2X0 = 0.; + std::vector layerStats(lutA->getNLayers()); + std::vector allCells; + + for (int il = 0; il < lutA->getNLayers(); il++) { + const auto& la = lutA->getLayer(il); + const auto& lb = lutB->getLayer(il); + if (la.getNZBins() != lb.getNZBins() || la.getNPhiBins() != lb.getNPhiBins()) { + LOG(error) << "Layer " << il << " segmentation differs: " + << la.getNZBins() << "x" << la.getNPhiBins() << " vs " + << lb.getNZBins() << "x" << lb.getNPhiBins(); + return false; + } + auto& ls = layerStats[il]; + ls.rMin = la.getRMin(); + ls.rMax = la.getRMax(); + + for (int iz = 0; iz < la.getNZBins(); iz++) { + for (int ip = 0; ip < la.getNPhiBins(); ip++) { + const auto& ca = la.getCellPhiBin(ip, iz); + const auto& cb = lb.getCellPhiBin(ip, iz); + nCells++; + + auto rel = [](float a, float b) { + const float den = std::max(std::abs(a), std::abs(b)); + return den > 0.f ? std::abs(a - b) / den : 0.f; + }; + const double rRho = rel(ca.meanRho, cb.meanRho); + const double rX = rel(ca.meanX2X0, cb.meanX2X0); + maxRelRho = std::max(maxRelRho, rRho); + maxRelX2X0 = std::max(maxRelX2X0, rX); + ls.maxRelRho = std::max(ls.maxRelRho, rRho); + ls.maxRelX2X0 = std::max(ls.maxRelX2X0, rX); + + if (rRho > tol || rX > tol) { + ls.nBad++; + nBad++; + } + allCells.push_back({il, iz, ip, ca.meanRho, cb.meanRho, ca.meanX2X0, cb.meanX2X0, rRho, rX}); + } + } + } + + const int nw = std::min(nWorst, (int)allCells.size()); + std::partial_sort(allCells.begin(), allCells.begin() + nw, allCells.end(), + [](const CellDiff& a, const CellDiff& b) { return a.score() > b.score(); }); + printf("--- %d worst cells (by max relative deviation) ---\n", nw); + for (int i = 0; i < nw; i++) { + const auto& c = allCells[i]; + printf("Lr %3d iz %4d ip %4d : rho %.9g vs %.9g (rel %.3g) | x2x0 %.9g vs %.9g (rel %.3g)\n", + c.layer, c.iz, c.ip, c.rhoA, c.rhoB, c.rRho, c.x2x0A, c.x2x0B, c.rX); + } + + printf("--- per-layer summary (%d layers) ---\n", lutA->getNLayers()); + for (int il = 0; il < lutA->getNLayers(); il++) { + const auto& ls = layerStats[il]; + printf("Lr %3d %8.3fgetNLayers()); + printf("Max relative difference: meanRho %.3g, meanX2X0 %.3g (tolerance %.3g)\n", maxRelRho, maxRelX2X0, tol); + + if (!csvSummary.empty()) { + const bool writeHeader = gSystem->AccessPathName(csvSummary.c_str()); // true if it does NOT exist + std::ofstream csv(csvSummary, std::ios::app); + if (writeHeader) { + csv << "fileA,fileB,nLayers,nCells,nBad,maxRelRho,maxRelX2X0,tol\n"; + } + csv << fileA << "," << fileB << "," << lutA->getNLayers() << "," << nCells << "," << nBad << "," + << maxRelRho << "," << maxRelX2X0 << "," << tol << "\n"; + } + + if (nBad) { + LOG(error) << nBad << " cells differ beyond tolerance"; + return false; + } + LOG(info) << "LUTs agree"; + return true; +} diff --git a/Detectors/CMakeLists.txt b/Detectors/CMakeLists.txt index eef692ff18ca7..9143761508998 100644 --- a/Detectors/CMakeLists.txt +++ b/Detectors/CMakeLists.txt @@ -25,6 +25,7 @@ add_subdirectory(TOF) add_subdirectory(ZDC) add_subdirectory(ITSMFT) +add_subdirectory(External) # sensitive external (CAD-derived) detectors; uses ITSMFT hit type add_subdirectory(TRD) add_subdirectory(MUON) diff --git a/Detectors/Calibration/include/DetectorsCalibration/IntegratedClusterCalibrator.h b/Detectors/Calibration/include/DetectorsCalibration/IntegratedClusterCalibrator.h index 8e6948ca5a418..1c0a46b68d840 100644 --- a/Detectors/Calibration/include/DetectorsCalibration/IntegratedClusterCalibrator.h +++ b/Detectors/Calibration/include/DetectorsCalibration/IntegratedClusterCalibrator.h @@ -365,6 +365,10 @@ struct TimeSeriesITSTPC { std::vector vertexY_ITSTPC_RMS; ///< vertex y RMS with ITS-TPC cut (nContributorsITS + nContributorsITSTPC)<0.95 std::vector vertexZ_ITSTPC_RMS; ///< vertex z RMS with ITS-TPC cut (nContributorsITS + nContributorsITSTPC)<0.95 + std::vector nITSTPCBasedPVContributors; ///< number of ITS-TPC-based PV contributors (denominator for TRD matching fraction) + std::vector nITSTPCWithTRDPVContributors; ///< number of ITS-TPC-TRD PV contributors (numerator for TRD matching fraction) + std::vector fracTRD; ///< fraction of ITS-TPC PV contributors with TRD match (NaN if denominator=0) + int quantileValues = 23; /// nVertexContributors_Quantiles; ///< number of primary vertices for quantiles 0.1, 0.2, ... 0.9 and truncated mean values 0.05->0.95, 0.1->0.9, 0.2->0.8 @@ -498,12 +502,15 @@ struct TimeSeriesITSTPC { vertexX_ITSTPC_RMS.resize(nTotalVtx); vertexY_ITSTPC_RMS.resize(nTotalVtx); vertexZ_ITSTPC_RMS.resize(nTotalVtx); + nITSTPCBasedPVContributors.resize(nTotalVtx); + nITSTPCWithTRDPVContributors.resize(nTotalVtx); + fracTRD.resize(nTotalVtx); const int nTotalQ = quantileValues * nTotal / mTSTPC.getNBins(); nVertexContributors_Quantiles.resize(nTotalQ); } - ClassDefNV(TimeSeriesITSTPC, 7); + ClassDefNV(TimeSeriesITSTPC, 8); }; } // end namespace tpc diff --git a/Detectors/External/CMakeLists.txt b/Detectors/External/CMakeLists.txt new file mode 100644 index 0000000000000..ea5f0b53e2b8e --- /dev/null +++ b/Detectors/External/CMakeLists.txt @@ -0,0 +1,22 @@ +# Copyright 2019-2020 CERN and copyright holders of ALICE O2. +# See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +# All rights not expressly granted are reserved. +# +# This software is distributed under the terms of the GNU General Public +# License v3 (GPL Version 3), copied verbatim in the file "COPYING". +# +# In applying this license CERN does not waive the privileges and immunities +# granted to it by virtue of its status as an Intergovernmental Organization +# or submit itself to any jurisdiction. + +o2_add_library(ExternalDetectors + SOURCES src/ExternalDetector.cxx + PUBLIC_LINK_LIBRARIES O2::DetectorsBase + O2::SimulationDataFormat + O2::CommonUtils + RapidJSON::RapidJSON) + +o2_target_root_dictionary(ExternalDetectors + HEADERS include/ExternalDetectors/Hit.h + include/ExternalDetectors/ExternalDetector.h + LINKDEF src/ExternalDetectorsLinkDef.h) diff --git a/Detectors/External/include/ExternalDetectors/ExternalDetector.h b/Detectors/External/include/ExternalDetectors/ExternalDetector.h new file mode 100644 index 0000000000000..fbfc30bd5e148 --- /dev/null +++ b/Detectors/External/include/ExternalDetectors/ExternalDetector.h @@ -0,0 +1,176 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file ExternalDetector.h +/// \brief Sensitive detector built from an externally provided (CAD-derived) geometry +/// +/// ExternalDetector is the sensitive counterpart of o2::passive::ExternalModule. +/// It injects a CAD-derived TGeo geometry (produced by scripts/geometry/O2_CADtoTGeo.py) +/// and turns a configurable set of its volumes (selected by medium or volume name) into +/// sensitive volumes which produce hits. It derives from o2::base::DetImpl, so it +/// transparently participates in the full o2-sim hit forwarding/merging machinery +/// (FairMQ serialization, sub-event merging, ...). +/// +/// All instances share one generic hit type (o2::ext::Hit) so that an arbitrary number +/// of external detectors can coexist (each tied to a different o2::detectors::DetID) +/// without the hit merger needing to know more than this single wire format. +/// +/// The sensitive action itself is configurable: by default a generic entrance/exit hit +/// is produced, but a user can instead provide a ROOT macro (loaded at runtime via +/// o2::conf::GetFromMacro, the same mechanism used for generator/stepping hooks) whose +/// function receives the detector instance and may query the TVirtualMC singleton and +/// call addHit(...) to implement an arbitrary sensitive action -- without recompiling O2. + +#ifndef ALICEO2_EXT_EXTERNALDETECTOR_H +#define ALICEO2_EXT_EXTERNALDETECTOR_H + +#include "DetectorsBase/Detector.h" // for DetImpl +#include "DetectorsCommonDataFormats/DetID.h" // for DetID +#include "ExternalDetectors/Hit.h" // for the generic external hit type + +#include "Rtypes.h" +#include "TLorentzVector.h" + +#include +#include +#include +#include +#include + +class FairVolume; +class TGeoMatrix; +class TVector3; + +namespace o2::ext +{ + +/// Configuration of a single sensitive external detector. +struct ExternalDetectorOptions { + std::string root_macro_file; // ROOT macro describing the CAD geometry (O2_CADtoTGeo.py output) + std::string anchor_volume; // existing volume into which the geometry is hooked + TGeoMatrix const* placement = nullptr; // placement of the geometry inside the anchor (may be null) + std::vector sensitiveMedia; // media (substring match on the medium name) to be made sensitive + std::vector sensitiveVolumes; // volumes (substring match on the volume name) to be made sensitive + int detID = o2::detectors::DetID::ITS; // DetID this detector's hits are tied to (identity / output format) + std::string sensitiveMacro; // optional ROOT macro implementing the sensitive action + std::string sensitiveFunction; // global function in the macro returning the action (default "sensitiveAction()") +}; + +class ExternalDetector : public o2::base::DetImpl +{ + public: + /// Signature of a (JIT-able) sensitive action. The function is handed the detector + /// instance and is expected to query the TVirtualMC singleton (TVirtualMC::GetMC()) + /// for the current step and to call addHit(...) to produce hits. Returning true means + /// a hit-relevant step was processed (mirrors the ProcessHits return value). + using SensitiveFcn = std::function; + + ExternalDetector(const char* name, const char* title, ExternalDetectorOptions options); + ExternalDetector(); + ~ExternalDetector() override; + + /// Build a list of sensitive external detectors from a JSON description file. + /// The file must contain an "externalDetectors" array; each entry needs at least + /// "name", "macro", "anchor" and at least one of "sensitiveMedia" / "sensitiveVolumes" + /// (arrays of substrings matched against medium / volume names); an optional + /// "detID" (name, default "ITS") ties the hit output to an existing detector, and + /// an optional "placement" object may carry "translation"/"rotation_deg". + /// Ownership of the returned detectors is transferred to the caller. + static std::vector createFromJSON(const std::string& jsonfile); + + /// Build the CAD geometry, remap its media and register the sensitive volumes. + void ConstructGeometry() override; + + /// Resolve the Monte Carlo volume IDs of the sensitive volumes. + void InitializeO2Detector() override; + + /// Called for each tracking step; produces hits in the sensitive volumes. + Bool_t ProcessHits(FairVolume* v = nullptr) override; + + /// Register the hit collection with the FairRootManager. + void Register() override; + + /// Get the produced hit collection (probe interface used by DetImpl). + std::vector* getHits(Int_t iColl) const + { + if (iColl == 0) { + return mHits; + } + return nullptr; + } + + void Reset() override; + void EndOfEvent() override; + + void FinishPrimary() override {} + void BeginPrimary() override {} + void PostTrack() override {} + void PreTrack() override {} + + /// \name Helpers usable from a user-provided sensitive-action macro + /// These wrap the bookkeeping a sensitive action typically needs so that a macro can + /// stay focused on physics and the TVirtualMC queries. + ///@{ + /// Append a hit to the output collection and flag the MCTrack as having left a hit + /// in this detector. Returns a pointer to the stored hit. + o2::ext::Hit* addHit(int trackID, int sensorID, const TVector3& startPos, const TVector3& endPos, + const TVector3& startMom, double startE, double endTime, double eLoss, + unsigned char startStatus, unsigned char endStatus, int pdg = 0, float length = 0.f); + + /// Running sensor index of the volume currently being processed, or -1 if the current + /// volume is not one of the configured sensitive volumes. + int currentSensorID() const; + + /// MCTrack number of the track currently being stepped. + int currentTrackID() const; + ///@} + + protected: + /// the built-in sensitive action used when no macro is configured (generic entrance/exit hit) + Bool_t defaultProcessHits(); + + /// recursively collect names of volumes whose medium matches the configured sensitive media + void collectSensitiveVolumeNames(TGeoVolume* vol, std::set& visited); + + ExternalDetectorOptions mOptions; + + std::vector mSensitiveVolumeNames; //! names of the volumes to be made sensitive (filled at geometry build) + std::set mSensitiveVolIDs; //! MC volume IDs of the sensitive volumes + std::unordered_map mVolID2SensorID; //! dense sensor index per sensitive MC volume ID + + /// transient data about a track passing a sensor (mirrors the ITS approach) + struct TrackData { + bool mHitStarted; //! hit creation started + unsigned char mTrkStatusStart; //! track status flag at entrance + TLorentzVector mPositionStart; //! position at entrance + TLorentzVector mMomentumStart; //! momentum at entrance + double mEnergyLoss; //! accumulated energy loss + } mTrackData; //! + + std::vector* mHits = nullptr; //! container for produced hits + + SensitiveFcn mSensitiveAction; //! optional user-provided sensitive action (loaded from a macro) + FairVolume* mCurrentVolume = nullptr; //! volume currently passed to ProcessHits (for the action helpers) + + int mStepCount = 0; //! number of stepping calls inside our sensitive volumes this event (probe) + + private: + ExternalDetector(const ExternalDetector&); + ExternalDetector& operator=(const ExternalDetector&); + + template + friend class o2::base::DetImpl; + ClassDefOverride(ExternalDetector, 1); +}; + +} // namespace o2::ext + +#endif diff --git a/Detectors/External/include/ExternalDetectors/Hit.h b/Detectors/External/include/ExternalDetectors/Hit.h new file mode 100644 index 0000000000000..4a77f85050f06 --- /dev/null +++ b/Detectors/External/include/ExternalDetectors/Hit.h @@ -0,0 +1,140 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file Hit.h +/// \brief Generic hit type for externally injected (CAD-derived) sensitive detectors +/// +/// o2::ext::Hit is a deliberately rich, detector-agnostic hit. A single external +/// hit type lets an arbitrary number of o2::ext::ExternalDetector instances (each +/// tied to a different DetID) share one wire format, so that the o2-sim hit merger +/// only ever has to know how to (de)serialize this one type, independently of how +/// many external detectors are configured or what their sensitive action does. +/// +/// It stores entrance and exit position, the momentum, energy and energy loss, the +/// time, the track length in the volume, the PDG code and the MC status flags at +/// entrance/exit, so that most information an external sensitive action might want +/// to keep is available downstream. + +#ifndef ALICEO2_EXT_HIT_H +#define ALICEO2_EXT_HIT_H + +#include "SimulationDataFormat/BaseHits.h" // for BasicXYZEHit +#include "CommonUtils/ShmAllocator.h" +#include "Rtypes.h" +#include "TVector3.h" +#include + +namespace o2::ext +{ + +class Hit : public o2::BasicXYZEHit +{ + public: + enum HitStatus_t { + kTrackEntering = 0x1, + kTrackInside = 0x1 << 1, + kTrackExiting = 0x1 << 2, + kTrackOut = 0x1 << 3, + kTrackStopped = 0x1 << 4, + kTrackAlive = 0x1 << 5 + }; + + Hit() = default; + + /// \param trackID index of the MCTrack + /// \param sensorID index of the sensitive volume (per-detector running id) + /// \param startPos coordinates at entrance to the active volume [cm] + /// \param endPos coordinates at exit of the active volume [cm] + /// \param startMom momentum of the track at entrance [GeV] + /// \param startE total energy at entrance [GeV] + /// \param endTime time at exit [ns] + /// \param eLoss energy deposited in the volume [GeV] + /// \param startStatus MC status flags at entrance + /// \param endStatus MC status flags at exit + /// \param pdg PDG code of the track (optional) + /// \param length track length inside the volume [cm] (optional) + Hit(int trackID, unsigned short sensorID, const TVector3& startPos, const TVector3& endPos, + const TVector3& startMom, double startE, double endTime, double eLoss, + unsigned char startStatus, unsigned char endStatus, int pdg = 0, float length = 0.f) + : BasicXYZEHit(endPos.X(), endPos.Y(), endPos.Z(), endTime, eLoss, trackID, sensorID), + mMomentum(startMom.Px(), startMom.Py(), startMom.Pz()), + mPosStart(startPos.X(), startPos.Y(), startPos.Z()), + mE(startE), + mLength(length), + mPdg(pdg), + mTrackStatusEnd(endStatus), + mTrackStatusStart(startStatus) + { + } + + // entrance position + math_utils::Point3D GetPosStart() const { return mPosStart; } + float GetStartX() const { return mPosStart.X(); } + float GetStartY() const { return mPosStart.Y(); } + float GetStartZ() const { return mPosStart.Z(); } + void SetPosStart(const math_utils::Point3D& p) { mPosStart = p; } + + // momentum / energy + math_utils::Vector3D GetMomentum() const { return mMomentum; } + math_utils::Vector3D& GetMomentum() { return mMomentum; } + float GetPx() const { return mMomentum.X(); } + float GetPy() const { return mMomentum.Y(); } + float GetPz() const { return mMomentum.Z(); } + float GetE() const { return mE; } + float GetTotalEnergy() const { return mE; } + + // extra bookkeeping + float GetLength() const { return mLength; } + void SetLength(float l) { mLength = l; } + int GetPdg() const { return mPdg; } + void SetPdg(int pdg) { mPdg = pdg; } + + // status flags + unsigned char GetStatusStart() const { return mTrackStatusStart; } + unsigned char GetStatusEnd() const { return mTrackStatusEnd; } + bool IsEntering() const { return mTrackStatusEnd & kTrackEntering; } + bool IsInside() const { return mTrackStatusEnd & kTrackInside; } + bool IsExiting() const { return mTrackStatusEnd & kTrackExiting; } + bool IsOut() const { return mTrackStatusEnd & kTrackOut; } + bool IsStopped() const { return mTrackStatusEnd & kTrackStopped; } + bool IsAlive() const { return mTrackStatusEnd & kTrackAlive; } + + friend std::ostream& operator<<(std::ostream& of, const Hit& point) + { + of << "-I- o2::ext::Hit for track " << point.GetTrackID() << " in sensor " << point.GetDetectorID(); + return of; + } + + private: + math_utils::Vector3D mMomentum; ///< momentum at entrance + math_utils::Point3D mPosStart; ///< position at entrance (base mPos holds the exit position) + float mE; ///< total energy at entrance + float mLength; ///< track length inside the volume + int mPdg; ///< PDG code of the track + unsigned char mTrackStatusEnd; ///< MC status flag at exit + unsigned char mTrackStatusStart; ///< MC status flag at entrance + + ClassDefNV(Hit, 1); +}; + +} // namespace o2::ext + +#ifdef USESHM +namespace std +{ +template <> +class allocator : public o2::utils::ShmAllocator +{ +}; +} // namespace std +#endif + +#endif // ALICEO2_EXT_HIT_H diff --git a/Detectors/External/macro/sensitiveActionExample.macro b/Detectors/External/macro/sensitiveActionExample.macro new file mode 100644 index 0000000000000..0f496bfcece0a --- /dev/null +++ b/Detectors/External/macro/sensitiveActionExample.macro @@ -0,0 +1,68 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file sensitiveActionExample.macro +/// \brief Example sensitive action for an o2::ext::ExternalDetector +/// +/// Point this macro at an "externalDetectors" entry via the "sensitiveMacro" key +/// (an absolute path, or one using shell variables such as $O2_ROOT, is resolved at runtime): +/// "sensitiveMacro": ".../Detectors/External/macro/sensitiveActionExample.macro" +/// +/// The global function sensitiveAction() returns the callable that o2-sim invokes for +/// every tracking step inside one of the detector's sensitive volumes. It is loaded at +/// runtime with o2::conf::GetFromMacro (the same just-in-time mechanism used for +/// generator and stepping hooks), so the sensitive logic can be changed without +/// recompiling O2. +/// +/// Inside the action you have the full TVirtualMC singleton available (exactly like a +/// hand-written ProcessHits) plus a few convenience helpers on the detector: +/// - det->currentSensorID() : running index of the current sensitive volume (-1 if none) +/// - det->currentTrackID() : MCTrack number of the track being stepped +/// - det->addHit(...) : append an o2::ext::Hit and flag the MCTrack +/// +/// This trivial example records one hit each time a charged particle enters a sensitive +/// volume. + +#if !defined(__CLING__) || defined(__ROOTCLING__) +#include "ExternalDetectors/ExternalDetector.h" +#include "ExternalDetectors/Hit.h" +#include +#include +#include +#endif + +// NOTE: the return type must be spelled exactly as the typedef name passed to +// GetFromMacro ("o2::ext::ExternalDetector::SensitiveFcn") -- the loader compares the +// function's return-type name textually, so std::function<...> would not match. +o2::ext::ExternalDetector::SensitiveFcn sensitiveAction() +{ + return [](o2::ext::ExternalDetector* det) -> bool { + auto vmc = TVirtualMC::GetMC(); + if (vmc->TrackCharge() == 0) { + return false; // ignore neutral particles + } + if (!vmc->IsTrackEntering()) { + return false; // record only the entrance point in this example + } + const int sensor = det->currentSensorID(); + if (sensor < 0) { + return false; // current volume is not one of our sensitive volumes + } + TLorentzVector pos, mom; + vmc->TrackPosition(pos); + vmc->TrackMomentum(mom); + // a point-like hit at the entrance (start == end position, no accumulated energy loss) + det->addHit(det->currentTrackID(), sensor, pos.Vect(), pos.Vect(), mom.Vect(), + mom.E(), pos.T(), 0. /*eLoss*/, o2::ext::Hit::kTrackEntering, o2::ext::Hit::kTrackEntering, + vmc->TrackPid(), vmc->TrackLength()); + return true; + }; +} diff --git a/Detectors/External/src/ExternalDetector.cxx b/Detectors/External/src/ExternalDetector.cxx new file mode 100644 index 0000000000000..f79d6ce6f2505 --- /dev/null +++ b/Detectors/External/src/ExternalDetector.cxx @@ -0,0 +1,451 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include "ExternalDetectors/ExternalDetector.h" +#include "DetectorsBase/CADGeometryUtils.h" +#include "DetectorsBase/Stack.h" +#include "CommonUtils/ConfigurationMacroHelper.h" +#include "CommonUtils/FileSystemUtils.h" +#include "CommonUtils/ShmManager.h" +#include "CommonUtils/ShmAllocator.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace o2::ext +{ + +ExternalDetector::ExternalDetector(const char* name, const char* title, ExternalDetectorOptions options) + : o2::base::DetImpl(name, true), + mOptions(options), + mTrackData(), + mHits(o2::utils::createSimVector()) +{ + (void)title; // the FairModule title is the second base ctor argument; kept for symmetry with other detectors + // Decouple the user-facing FairModule name (e.g. "IRIS") from the DetId: the base + // ctor derives fDetId from the name which is generally not a registered DetID, so we + // explicitly tie this detector to the configured (existing) DetID. This is what makes + // the hit output format / identity well defined, as discussed. + fDetId = mOptions.detID; +} + +ExternalDetector::ExternalDetector() + : o2::base::DetImpl("EXTDET", true), + mTrackData(), + mHits(o2::utils::createSimVector()) +{ +} + +ExternalDetector::ExternalDetector(const ExternalDetector& rhs) + : o2::base::DetImpl(rhs), + mOptions(rhs.mOptions), + mSensitiveVolumeNames(rhs.mSensitiveVolumeNames), + mSensitiveVolIDs(rhs.mSensitiveVolIDs), + mVolID2SensorID(rhs.mVolID2SensorID), + mTrackData(), + mHits(o2::utils::createSimVector()) +{ +} + +ExternalDetector::~ExternalDetector() +{ + if (mHits) { + o2::utils::freeSimVector(mHits); + } +} + +void ExternalDetector::collectSensitiveVolumeNames(TGeoVolume* vol, std::set& visited) +{ + if (!vol || visited.count(vol)) { + return; + } + visited.insert(vol); + + bool sensitive = false; + // match by volume name + const std::string volname = vol->GetName(); + for (const auto& token : mOptions.sensitiveVolumes) { + if (!token.empty() && volname.find(token) != std::string::npos) { + sensitive = true; + break; + } + } + // otherwise match by medium name + if (!sensitive) { + if (auto medium = vol->GetMedium()) { + const std::string medname = medium->GetName(); + for (const auto& token : mOptions.sensitiveMedia) { + if (!token.empty() && medname.find(token) != std::string::npos) { + sensitive = true; + break; + } + } + } + } + if (sensitive) { + mSensitiveVolumeNames.emplace_back(volname); + } + + const int nd = vol->GetNdaughters(); + for (int i = 0; i < nd; ++i) { + if (auto node = vol->GetNode(i)) { + collectSensitiveVolumeNames(node->GetVolume(), visited); + } + } +} + +void ExternalDetector::ConstructGeometry() +{ + // build the CAD geometry and obtain its top volume + auto module_top = o2::base::buildCADVolumeFromMacro(mOptions.root_macro_file, GetName()); + if (!module_top) { + LOG(error) << "No geometry could be built for external detector " << GetName(); + return; + } + + // bring the CAD media under O2's MaterialManager + o2::base::remapCADMedia(module_top, GetName()); + + // determine which volumes should become sensitive (selected by medium name) + mSensitiveVolumeNames.clear(); + std::set visited; + collectSensitiveVolumeNames(module_top, visited); + if (mSensitiveVolumeNames.empty()) { + LOG(warning) << "External detector " << GetName() << ": no volume matched the configured sensitive media; " + << "no hits will be produced"; + } else { + LOG(info) << "External detector " << GetName() << ": " << mSensitiveVolumeNames.size() + << " sensitive volume(s) selected"; + } + + // place it into the provided anchor volume (needs to exist) + auto anchor = gGeoManager->FindVolumeFast(mOptions.anchor_volume.c_str()); + if (!anchor) { + LOG(error) << "Anchor volume " << mOptions.anchor_volume << " not found. Aborting"; + return; + } + anchor->AddNode(module_top, 1, const_cast(mOptions.placement)); +} + +void ExternalDetector::InitializeO2Detector() +{ + // resolve the MC volume IDs of the sensitive volumes and register them with FairRoot + mSensitiveVolIDs.clear(); + mVolID2SensorID.clear(); + int sensorID = 0; + for (const auto& name : mSensitiveVolumeNames) { + const int volID = registerSensitiveVolumeAndGetVolID(name); + if (volID <= 0) { + continue; + } + mSensitiveVolIDs.insert(volID); + mVolID2SensorID[volID] = sensorID++; + LOG(info) << "External detector " << GetName() << ": registered sensitive volume '" << name + << "' (MC volID " << volID << ", sensor " << mVolID2SensorID[volID] << ")"; + } + + // optionally load a user-provided sensitive action from a ROOT macro (same mechanism as + // generator/stepping hooks). When given, it fully replaces the built-in action. + if (!mOptions.sensitiveMacro.empty()) { + const auto file = o2::utils::expandShellVarsInFileName(mOptions.sensitiveMacro); + const auto func = mOptions.sensitiveFunction.empty() ? std::string("sensitiveAction()") : mOptions.sensitiveFunction; + const auto unique = std::string("o2ext_sensitive_action_") + GetName(); + mSensitiveAction = o2::conf::GetFromMacro(file, func, "o2::ext::ExternalDetector::SensitiveFcn", unique); + if (mSensitiveAction) { + LOG(info) << "External detector " << GetName() << ": using sensitive action '" << func + << "' from macro '" << file << "'"; + } else { + LOG(fatal) << "External detector " << GetName() << ": could not load sensitive action '" << func + << "' from macro '" << file << "'"; + } + } +} + +Bool_t ExternalDetector::ProcessHits(FairVolume* vol) +{ + // This method is called from the MC stepping for the registered sensitive volumes. + // Remember the current volume so the action helpers (currentSensorID()) can resolve it, + // then either run the user-provided action or the built-in one. + mCurrentVolume = vol; + ++mStepCount; // probe: count stepping calls inside our sensitive volumes + if (mSensitiveAction) { + return mSensitiveAction(this) ? kTRUE : kFALSE; + } + return defaultProcessHits(); +} + +Bool_t ExternalDetector::defaultProcessHits() +{ + if (!(fMC->TrackCharge())) { + return kFALSE; + } + + const int sensorID = currentSensorID(); + if (sensorID < 0) { + return kFALSE; // not one of our sensitive volumes + } + + bool startHit = false, stopHit = false; + unsigned char status = 0; + if (fMC->IsTrackEntering()) { + status |= o2::ext::Hit::kTrackEntering; + } + if (fMC->IsTrackInside()) { + status |= o2::ext::Hit::kTrackInside; + } + if (fMC->IsTrackExiting()) { + status |= o2::ext::Hit::kTrackExiting; + } + if (fMC->IsTrackOut()) { + status |= o2::ext::Hit::kTrackOut; + } + if (fMC->IsTrackStop()) { + status |= o2::ext::Hit::kTrackStopped; + } + if (fMC->IsTrackAlive()) { + status |= o2::ext::Hit::kTrackAlive; + } + + // track is entering or created in the volume + if ((status & o2::ext::Hit::kTrackEntering) || (status & o2::ext::Hit::kTrackInside && !mTrackData.mHitStarted)) { + startHit = true; + } else if ((status & (o2::ext::Hit::kTrackExiting | o2::ext::Hit::kTrackOut | o2::ext::Hit::kTrackStopped))) { + stopHit = true; + } + + // increment energy loss at all steps except entrance + if (!startHit) { + mTrackData.mEnergyLoss += fMC->Edep(); + } + if (!(startHit | stopHit)) { + return kFALSE; // do nothing + } + + if (startHit) { + mTrackData.mEnergyLoss = 0.; + fMC->TrackMomentum(mTrackData.mMomentumStart); + fMC->TrackPosition(mTrackData.mPositionStart); + mTrackData.mTrkStatusStart = status; + mTrackData.mHitStarted = true; + } + if (stopHit) { + TLorentzVector positionStop; + fMC->TrackPosition(positionStop); + addHit(currentTrackID(), sensorID, mTrackData.mPositionStart.Vect(), positionStop.Vect(), + mTrackData.mMomentumStart.Vect(), mTrackData.mMomentumStart.E(), positionStop.T(), + mTrackData.mEnergyLoss, mTrackData.mTrkStatusStart, status, fMC->TrackPid(), fMC->TrackLength()); + mTrackData.mHitStarted = false; + } + return kTRUE; +} + +int ExternalDetector::currentSensorID() const +{ + const int volID = mCurrentVolume ? mCurrentVolume->getMCid() : -1; + auto it = mVolID2SensorID.find(volID); + return it == mVolID2SensorID.end() ? -1 : it->second; +} + +int ExternalDetector::currentTrackID() const +{ + return static_cast(fMC->GetStack())->GetCurrentTrackNumber(); +} + +o2::ext::Hit* ExternalDetector::addHit(int trackID, int sensorID, const TVector3& startPos, const TVector3& endPos, + const TVector3& startMom, double startE, double endTime, double eLoss, + unsigned char startStatus, unsigned char endStatus, int pdg, float length) +{ + mHits->emplace_back(trackID, sensorID, startPos, endPos, startMom, startE, endTime, eLoss, + startStatus, endStatus, pdg, length); + // register that this track left a hit in our detector (sets the hit bit on the MCTrack) + static_cast(fMC->GetStack())->addHit(GetDetId()); + return &(mHits->back()); +} + +void ExternalDetector::Register() +{ + // Create a branch (named "Hit") holding the produced hits. + if (FairRootManager::Instance()) { + FairRootManager::Instance()->RegisterAny(addNameTo("Hit").data(), mHits, kTRUE); + } +} + +void ExternalDetector::Reset() +{ + if (!o2::utils::ShmManager::Instance().isOperational()) { + mHits->clear(); + } +} + +void ExternalDetector::EndOfEvent() +{ + // probe: report how often our sensitive volumes were stepped through and how many hits resulted + LOG(info) << "External detector " << GetName() << " EndOfEvent: " << mStepCount + << " sensitive step(s) -> " << (mHits ? mHits->size() : 0) << " hit(s)"; + mStepCount = 0; + Reset(); +} + +namespace +{ +// Build a TGeoCombiTrans from an optional JSON "placement" object carrying +// "translation":[x,y,z] (cm) and/or "rotation_deg":[rx,ry,rz] (deg, applied X,Y,Z). +TGeoMatrix* makePlacementFromJSON(const rapidjson::Value& placement) +{ + auto combi = new TGeoCombiTrans(); + if (placement.HasMember("rotation_deg") && placement["rotation_deg"].IsArray()) { + const auto& r = placement["rotation_deg"]; + if (r.Size() == 3) { + combi->RotateX(r[0].GetDouble()); + combi->RotateY(r[1].GetDouble()); + combi->RotateZ(r[2].GetDouble()); + } else { + LOG(warning) << "ExternalDetector placement 'rotation_deg' must have 3 entries; ignoring"; + } + } + if (placement.HasMember("translation") && placement["translation"].IsArray()) { + const auto& t = placement["translation"]; + if (t.Size() == 3) { + combi->SetDx(t[0].GetDouble()); + combi->SetDy(t[1].GetDouble()); + combi->SetDz(t[2].GetDouble()); + } else { + LOG(warning) << "ExternalDetector placement 'translation' must have 3 entries; ignoring"; + } + } + return combi; +} +} // namespace + +std::vector ExternalDetector::createFromJSON(const std::string& jsonfile) +{ + std::vector result; + + auto expanded = o2::utils::expandShellVarsInFileName(jsonfile); + std::ifstream fileStream(expanded, std::ios::in); + if (!fileStream.is_open()) { + LOG(error) << "Cannot open external geometry config file '" << expanded << "'"; + return result; + } + + rapidjson::IStreamWrapper isw(fileStream); + rapidjson::Document doc; + doc.ParseStream(isw); + if (doc.HasParseError()) { + LOG(error) << "Error parsing external geometry JSON '" << expanded << "': " + << rapidjson::GetParseError_En(doc.GetParseError()) + << " (offset " << doc.GetErrorOffset() << ")"; + return result; + } + // the array of sensitive external detectors is optional (the same file may only + // configure passive external modules) + if (!doc.HasMember("externalDetectors")) { + return result; + } + if (!doc["externalDetectors"].IsArray()) { + LOG(error) << "External geometry JSON '" << expanded << "': 'externalDetectors' must be an array"; + return result; + } + + auto getString = [](const rapidjson::Value& v, const char* key) -> std::string { + if (v.HasMember(key) && v[key].IsString()) { + return v[key].GetString(); + } + return std::string(); + }; + + for (const auto& entry : doc["externalDetectors"].GetArray()) { + if (!entry.IsObject()) { + LOG(error) << "Skipping non-object entry in 'externalDetectors'"; + continue; + } + const auto name = getString(entry, "name"); + if (name.empty()) { + LOG(error) << "Skipping external detector entry without 'name'"; + continue; + } + ExternalDetectorOptions options; + options.root_macro_file = getString(entry, "macro"); + options.anchor_volume = getString(entry, "anchor"); + if (options.root_macro_file.empty() || options.anchor_volume.empty()) { + LOG(error) << "External detector '" << name << "' requires both 'macro' and 'anchor'; skipping"; + continue; + } + + if (entry.HasMember("sensitiveMedia") && entry["sensitiveMedia"].IsArray()) { + for (const auto& m : entry["sensitiveMedia"].GetArray()) { + if (m.IsString()) { + options.sensitiveMedia.emplace_back(m.GetString()); + } + } + } + if (entry.HasMember("sensitiveVolumes") && entry["sensitiveVolumes"].IsArray()) { + for (const auto& v : entry["sensitiveVolumes"].GetArray()) { + if (v.IsString()) { + options.sensitiveVolumes.emplace_back(v.GetString()); + } + } + } + if (options.sensitiveMedia.empty() && options.sensitiveVolumes.empty()) { + LOG(error) << "External detector '" << name + << "' requires a non-empty 'sensitiveMedia' or 'sensitiveVolumes' array; skipping"; + continue; + } + + const auto detIDName = getString(entry, "detID"); + if (!detIDName.empty()) { + const auto did = o2::detectors::DetID::nameToID(detIDName.c_str()); + if (did < 0 || did >= o2::detectors::DetID::nDetectors) { + LOG(error) << "External detector '" << name << "': unknown detID '" << detIDName << "'; skipping"; + continue; + } + options.detID = did; + } + + if (entry.HasMember("placement") && entry["placement"].IsObject()) { + options.placement = makePlacementFromJSON(entry["placement"]); + } + + // optional user-provided sensitive action (a ROOT macro). When absent, the built-in + // generic entrance/exit hit action is used. + options.sensitiveMacro = getString(entry, "sensitiveMacro"); + options.sensitiveFunction = getString(entry, "sensitiveFunction"); + + auto title = getString(entry, "title"); + if (title.empty()) { + title = name; + } + LOG(info) << "Configured external detector '" << name << "' from macro '" << options.root_macro_file + << "' anchored to '" << options.anchor_volume << "', tied to DetID '" + << o2::detectors::DetID::getName(options.detID) << "'"; + result.push_back(new ExternalDetector(name.c_str(), title.c_str(), options)); + } + return result; +} + +} // namespace o2::ext + +ClassImp(o2::ext::ExternalDetector); diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Hit.h b/Detectors/External/src/ExternalDetectorsLinkDef.h similarity index 62% rename from Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Hit.h rename to Detectors/External/src/ExternalDetectorsLinkDef.h index 402a343ead472..f6e13b70200fc 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Hit.h +++ b/Detectors/External/src/ExternalDetectorsLinkDef.h @@ -9,21 +9,15 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -/// \file Hit.h -/// \brief Definition of the TRK Hit class +#ifdef __CLING__ -#ifndef ALICEO2_TRK_HIT_H_ -#define ALICEO2_TRK_HIT_H_ +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; -#include "ITSMFTSimulation/Hit.h" - -namespace o2::trk -{ -class Hit : public o2::itsmft::Hit -{ - public: - using o2::itsmft::Hit::Hit; // Inherit constructors -}; -} // namespace o2::trk +#pragma link C++ class o2::ext::Hit + ; +#pragma link C++ class std::vector < o2::ext::Hit> + ; +#pragma link C++ class o2::ext::ExternalDetector + ; +#pragma link C++ class o2::base::DetImpl < o2::ext::ExternalDetector> + ; #endif diff --git a/Detectors/FIT/FT0/dcsmonitoring/src/FT0DCSMonitoringLinkDef.h b/Detectors/FIT/FT0/dcsmonitoring/src/FT0DCSMonitoringLinkDef.h index c85dd2d378ccb..50a9e7f35e25f 100644 --- a/Detectors/FIT/FT0/dcsmonitoring/src/FT0DCSMonitoringLinkDef.h +++ b/Detectors/FIT/FT0/dcsmonitoring/src/FT0DCSMonitoringLinkDef.h @@ -15,4 +15,6 @@ #pragma link off all classes; #pragma link off all functions; +#pragma link C++ class o2::ft0::FT0DCSConfigReader + ; + #endif diff --git a/Detectors/FIT/FV0/reconstruction/src/FV0ReconstructionLinkDef.h b/Detectors/FIT/FV0/reconstruction/src/FV0ReconstructionLinkDef.h index c85dd2d378ccb..f4551100c8cc5 100644 --- a/Detectors/FIT/FV0/reconstruction/src/FV0ReconstructionLinkDef.h +++ b/Detectors/FIT/FV0/reconstruction/src/FV0ReconstructionLinkDef.h @@ -15,4 +15,6 @@ #pragma link off all classes; #pragma link off all functions; +#pragma link C++ class o2::fv0::BaseRecoTask + ; + #endif diff --git a/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/StrangenessTrackingSpec.h b/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/StrangenessTrackingSpec.h index 8367cef7f51b0..bbc2cdc80995e 100644 --- a/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/StrangenessTrackingSpec.h +++ b/Detectors/GlobalTrackingWorkflow/include/GlobalTrackingWorkflow/StrangenessTrackingSpec.h @@ -50,6 +50,7 @@ class StrangenessTrackerSpec : public framework::Task private: void updateTimeDependentParams(framework::ProcessingContext& pc); + void storeConfigs(framework::ProcessingContext& pc); bool mUseMC = false; TStopwatch mTimer; diff --git a/Detectors/GlobalTrackingWorkflow/src/CosmicsMatchingSpec.cxx b/Detectors/GlobalTrackingWorkflow/src/CosmicsMatchingSpec.cxx index d10330db09ea2..338bb86bd8033 100644 --- a/Detectors/GlobalTrackingWorkflow/src/CosmicsMatchingSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/CosmicsMatchingSpec.cxx @@ -11,14 +11,18 @@ /// @file CosmicsMatchingSpec.cxx +#include +#include #include #include #include "TStopwatch.h" #include "GlobalTracking/MatchCosmics.h" +#include "GlobalTracking/MatchCosmicsParams.h" #include "DataFormatsITSMFT/TopologyDictionary.h" #include "DataFormatsTPC/Constants.h" #include "ReconstructionDataFormats/GlobalTrackID.h" #include "Framework/ConfigParamRegistry.h" +#include "Framework/DeviceSpec.h" #include "GlobalTrackingWorkflow/CosmicsMatchingSpec.h" #include "ReconstructionDataFormats/GlobalTrackAccessor.h" #include "ReconstructionDataFormats/GlobalTrackID.h" @@ -71,6 +75,7 @@ class CosmicsMatchingSpec : public Task private: void updateTimeDependentParams(ProcessingContext& pc); + void storeConfigs(ProcessingContext& pc); std::shared_ptr mDataRequest; std::shared_ptr mGGCCDBRequest; o2::tpc::VDriftHelper mTPCVDriftHelper{}; @@ -98,7 +103,7 @@ void CosmicsMatchingSpec::run(ProcessingContext& pc) RecoContainer recoData; recoData.collectData(pc, *mDataRequest.get()); updateTimeDependentParams(pc); // Make sure this is called after recoData.collectData, which may load some conditions - + storeConfigs(pc); mMatching.process(recoData); pc.outputs().snapshot(Output{"GLO", "COSMICTRC", 0}, mMatching.getCosmicTracks()); if (mUseMC) { @@ -107,6 +112,22 @@ void CosmicsMatchingSpec::run(ProcessingContext& pc) mTimer.Stop(); } +void CosmicsMatchingSpec::storeConfigs(ProcessingContext& pc) +{ + static bool first = true; + if (first) { + first = false; + if (pc.services().get().inputTimesliceId == 0) { + const auto& conf = MatchCosmicsParams::Instance(); + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, conf.getName()), conf.getName()); + TMap md; + md.SetOwnerKeyValue(); + md.Add(new TObjString(conf.getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(conf.getName()).c_str())); + pc.outputs().snapshot(Output{"META", "COSMICMATCHER", 0}, md); + } + } +} + void CosmicsMatchingSpec::updateTimeDependentParams(ProcessingContext& pc) { o2::base::GRPGeomHelper::instance().checkUpdates(pc); @@ -193,6 +214,8 @@ DataProcessorSpec getCosmicsMatchingSpec(GTrackID::mask_t src, bool usePV, bool o2::tpc::VDriftHelper::requestCCDBInputs(dataRequest->inputs); dataRequest->inputs.emplace_back("corrMap", o2::header::gDataOriginTPC, "TPCCORRMAP", 0, Lifetime::Timeframe); + outputs.emplace_back("META", "COSMICMATCHER", 0, Lifetime::Sporadic); + return DataProcessorSpec{ "cosmics-matcher", dataRequest->inputs, diff --git a/Detectors/GlobalTrackingWorkflow/src/PrimaryVertexingSpec.cxx b/Detectors/GlobalTrackingWorkflow/src/PrimaryVertexingSpec.cxx index c1d7b62bbf731..e92d5c35152e1 100644 --- a/Detectors/GlobalTrackingWorkflow/src/PrimaryVertexingSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/PrimaryVertexingSpec.cxx @@ -11,12 +11,15 @@ /// @file PrimaryVertexingSpec.cxx +#include +#include #include #include #include "DataFormatsGlobalTracking/RecoContainer.h" #include "DataFormatsGlobalTracking/RecoContainerCreateTracksVariadic.h" #include "DataFormatsITSMFT/TrkClusRef.h" #include "DataFormatsCalibration/MeanVertexObject.h" +#include "DataFormatsCalibration/MeanVertexBiasParam.h" #include "ReconstructionDataFormats/TrackTPCITS.h" #include "ReconstructionDataFormats/GlobalTrackID.h" #include "DetectorsBase/Propagator.h" @@ -59,6 +62,7 @@ class PrimaryVertexingSpec : public Task private: void updateTimeDependentParams(ProcessingContext& pc); + void storeConfigs(ProcessingContext& pc); std::shared_ptr mDataRequest; std::shared_ptr mGGCCDBRequest; o2::vertexing::PVertexer mVertexer; @@ -98,7 +102,7 @@ void PrimaryVertexingSpec::run(ProcessingContext& pc) o2::globaltracking::RecoContainer recoData; recoData.collectData(pc, *mDataRequest.get()); // select tracks of needed type, with minimal cuts, the real selected will be done in the vertexer updateTimeDependentParams(pc); // Make sure this is called after recoData.collectData, which may load some conditions - + storeConfigs(pc); std::vector tracks; std::vector tracksMCInfo; std::vector gids; @@ -180,6 +184,8 @@ void PrimaryVertexingSpec::run(ProcessingContext& pc) vertices[iv].setFlags(PVertex::UPCMode); } } + } else { + storeConfigs(pc); } pc.outputs().snapshot(Output{"GLO", "PVTX", 0}, vertices); @@ -197,12 +203,23 @@ void PrimaryVertexingSpec::run(ProcessingContext& pc) mVertexer.getTimeReAttach().CpuTime(), mVertexer.getTotTrials(), mVertexer.getNTZClusters(), mVertexer.getMaxTrialsPerCluster(), mVertexer.getLongestClusterTimeMS(), mVertexer.getLongestClusterMult(), mVertexer.getNIniFound(), mVertexer.getNKilledBCValid(), mVertexer.getNKilledIntCand(), mVertexer.getNKilledDebris(), mVertexer.getNKilledQuality(), mVertexer.getNKilledITSOnly()); +} +void PrimaryVertexingSpec::storeConfigs(ProcessingContext& pc) +{ static bool first = true; if (first) { first = false; + const auto& confPV = PVertexerParams::Instance(); + const auto& confMV = o2::dataformats::MeanVertexBiasParam::Instance(); if (pc.services().get().inputTimesliceId == 0) { - o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, PVertexerParams::Instance().getName()), PVertexerParams::Instance().getName()); + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, confPV.getName()), confPV.getName()); + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, confMV.getName()), confMV.getName()); + TMap md; + md.SetOwnerKeyValue(); + md.Add(new TObjString(confPV.getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(confPV.getName()).c_str())); + md.Add(new TObjString(confMV.getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(confMV.getName()).c_str())); + pc.outputs().snapshot(Output{"META", "PVERTEXER", 0}, md); } } } @@ -289,6 +306,8 @@ DataProcessorSpec getPrimaryVertexingSpec(GTrackID::mask_t src, bool skip, bool true); dataRequest->inputs.emplace_back("meanvtx", "GLO", "MEANVERTEX", 0, Lifetime::Condition, ccdbParamSpec("GLO/Calib/MeanVertex", {}, 1)); + outputs.emplace_back("META", "PVERTEXER", 0, Lifetime::Sporadic); + return DataProcessorSpec{ "primary-vertexing", dataRequest->inputs, diff --git a/Detectors/GlobalTrackingWorkflow/src/SecondaryVertexingSpec.cxx b/Detectors/GlobalTrackingWorkflow/src/SecondaryVertexingSpec.cxx index afce2861be2fb..3a9de8662f4b5 100644 --- a/Detectors/GlobalTrackingWorkflow/src/SecondaryVertexingSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/SecondaryVertexingSpec.cxx @@ -11,6 +11,8 @@ /// @file SecondaryVertexingSpec.cxx +#include +#include #include #include "DataFormatsCalibration/MeanVertexObject.h" #include "Framework/CCDBParamSpec.h" @@ -65,6 +67,7 @@ class SecondaryVertexingSpec : public Task void finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) final; private: + void storeConfigs(ProcessingContext& pc); void updateTimeDependentParams(ProcessingContext& pc); std::shared_ptr mDataRequest; std::shared_ptr mGGCCDBRequest; @@ -110,7 +113,7 @@ void SecondaryVertexingSpec::run(ProcessingContext& pc) o2::globaltracking::RecoContainer recoData; recoData.collectData(pc, *mDataRequest.get()); updateTimeDependentParams(pc); - + storeConfigs(pc); mVertexer.process(recoData, pc); mTimer.Stop(); @@ -119,15 +122,25 @@ void SecondaryVertexingSpec::run(ProcessingContext& pc) mVertexer.getNV0s(), calls[0] - fitCalls[0], mVertexer.getNCascades(), calls[1] - fitCalls[1], mVertexer.getN3Bodies(), calls[2] - fitCalls[2], mVertexer.getNStrangeTracks(), mTimer.CpuTime() - timeCPU0, mTimer.RealTime() - timeReal0); fitCalls = calls; +} +void SecondaryVertexingSpec::storeConfigs(ProcessingContext& pc) +{ static bool first = true; if (first) { first = false; if (pc.services().get().inputTimesliceId == 0) { - o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, SVertexerParams::Instance().getName()), SVertexerParams::Instance().getName()); + const auto& confSV = SVertexerParams::Instance(); + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, confSV.getName()), confSV.getName()); + TMap md; + md.SetOwnerKeyValue(); + md.Add(new TObjString(confSV.getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(confSV.getName()).c_str())); if (mEnableStrangenessTracking) { - o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, o2::strangeness_tracking::StrangenessTrackingParamConfig::Instance().getName()), o2::strangeness_tracking::StrangenessTrackingParamConfig::Instance().getName()); + const auto& confST = o2::strangeness_tracking::StrangenessTrackingParamConfig::Instance(); + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, confST.getName()), confST.getName()); + md.Add(new TObjString(confST.getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(confST.getName()).c_str())); } + pc.outputs().snapshot(Output{"META", "SVERTEXER", 0}, md); } } } @@ -298,6 +311,7 @@ DataProcessorSpec getSecondaryVertexingSpec(GTrackID::mask_t src, bool enableCas LOG(info) << "Strangeness tracker will use MC"; } } + outputs.emplace_back("META", "SVERTEXER", 0, Lifetime::Sporadic); return DataProcessorSpec{ "secondary-vertexing", diff --git a/Detectors/GlobalTrackingWorkflow/src/StrangenessTrackingSpec.cxx b/Detectors/GlobalTrackingWorkflow/src/StrangenessTrackingSpec.cxx index e313940b0a91e..c438f869773a5 100644 --- a/Detectors/GlobalTrackingWorkflow/src/StrangenessTrackingSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/StrangenessTrackingSpec.cxx @@ -11,6 +11,8 @@ /// \file StrangenessTrackingSpec.cxx /// \brief +#include +#include #include "TGeoGlobalMagField.h" #include "Framework/ConfigParamRegistry.h" #include "Field/MagneticField.h" @@ -20,6 +22,7 @@ #include "ITSWorkflow/TrackerSpec.h" #include "ITSWorkflow/TrackReaderSpec.h" #include "Framework/CCDBParamSpec.h" +#include "Framework/DeviceSpec.h" #include "DataFormatsParameters/GRPObject.h" #include "DataFormatsITSMFT/ROFRecord.h" @@ -68,7 +71,7 @@ void StrangenessTrackerSpec::run(framework::ProcessingContext& pc) o2::globaltracking::RecoContainer recoData; recoData.collectData(pc, *mDataRequest.get()); updateTimeDependentParams(pc); - + storeConfigs(pc); auto geom = o2::its::GeometryTGeo::Instance(); mTracker.loadData(recoData); mTracker.prepareITStracks(); @@ -83,6 +86,22 @@ void StrangenessTrackerSpec::run(framework::ProcessingContext& pc) mTimer.Stop(); } +void StrangenessTrackerSpec::storeConfigs(framework::ProcessingContext& pc) +{ + static bool first = true; + if (first) { + first = false; + if (pc.services().get().inputTimesliceId == 0) { + const auto& conf = StrangenessTrackingParamConfig::Instance(); + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, conf.getName()), conf.getName()); + TMap md; + md.SetOwnerKeyValue(); + md.Add(new TObjString(conf.getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(conf.getName()).c_str())); + pc.outputs().snapshot(Output{"META", "STRTRACKER", 0}, md); + } + } +} + ///_______________________________________ void StrangenessTrackerSpec::updateTimeDependentParams(ProcessingContext& pc) { @@ -162,6 +181,7 @@ DataProcessorSpec getStrangenessTrackerSpec(o2::dataformats::GlobalTrackID::mask outputs.emplace_back("GLO", "STRANGETRACKS_MC", 0, Lifetime::Timeframe); LOG(info) << "Strangeness tracker will use MC"; } + outputs.emplace_back("META", "STRTRACKER", 0, Lifetime::Sporadic); return DataProcessorSpec{ "strangeness-tracker", diff --git a/Detectors/GlobalTrackingWorkflow/src/TOFMatcherSpec.cxx b/Detectors/GlobalTrackingWorkflow/src/TOFMatcherSpec.cxx index 746e572c506b8..17ee87b8d52cd 100644 --- a/Detectors/GlobalTrackingWorkflow/src/TOFMatcherSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/TOFMatcherSpec.cxx @@ -13,6 +13,8 @@ #include #include +#include +#include #include "TStopwatch.h" #include "Framework/ConfigParamRegistry.h" #include "DetectorsBase/GeometryManager.h" @@ -68,6 +70,7 @@ class TOFMatcherSpec : public Task private: void updateTimeDependentParams(ProcessingContext& pc); + void storeConfigs(ProcessingContext& pc); std::shared_ptr mDataRequest; std::shared_ptr mGGCCDBRequest; o2::tpc::VDriftHelper mTPCVDriftHelper{}; @@ -141,6 +144,7 @@ void TOFMatcherSpec::run(ProcessingContext& pc) RecoContainer recoData; recoData.collectData(pc, *mDataRequest.get()); updateTimeDependentParams(pc); + storeConfigs(pc); auto creationTime = pc.services().get().creation; LOG(debug) << "isTrackSourceLoaded: TPC -> " << recoData.isTrackSourceLoaded(o2::dataformats::GlobalTrackID::Source::TPC); @@ -214,15 +218,23 @@ void TOFMatcherSpec::run(ProcessingContext& pc) pc.outputs().snapshot(Output{o2::header::gDataOriginTOF, "MATCHABLES_17", 0}, mMatcher.getMatchedTracksPair(17)); } + mTimer.Stop(); +} + +void TOFMatcherSpec::storeConfigs(ProcessingContext& pc) +{ static bool first = true; if (first) { first = false; if (pc.services().get().inputTimesliceId == 0) { - o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, MatchTOFParams::Instance().getName()), MatchTOFParams::Instance().getName()); + const auto& conf = MatchTOFParams::Instance(); + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, conf.getName()), conf.getName()); + TMap md; + md.SetOwnerKeyValue(); + md.Add(new TObjString(conf.getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(conf.getName()).c_str())); + pc.outputs().snapshot(Output{"META", "TOFMATCHER", 0}, md); } } - - mTimer.Stop(); } void TOFMatcherSpec::endOfStream(EndOfStreamContext& ec) @@ -309,6 +321,7 @@ DataProcessorSpec getTOFMatcherSpec(GID::mask_t src, bool useMC, bool useFIT, bo outputs.emplace_back(o2::header::gDataOriginTOF, "MATCHABLES_16", 0, Lifetime::Timeframe); outputs.emplace_back(o2::header::gDataOriginTOF, "MATCHABLES_17", 0, Lifetime::Timeframe); } + outputs.emplace_back("META", "TOFMATCHER", 0, Lifetime::Sporadic); return DataProcessorSpec{ "tof-matcher", diff --git a/Detectors/GlobalTrackingWorkflow/src/TPCITSMatchingSpec.cxx b/Detectors/GlobalTrackingWorkflow/src/TPCITSMatchingSpec.cxx index 079fe5455fd4a..7f63b61e02be0 100644 --- a/Detectors/GlobalTrackingWorkflow/src/TPCITSMatchingSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/TPCITSMatchingSpec.cxx @@ -12,9 +12,11 @@ /// @file TPCITSMatchingSpec.cxx #include - +#include +#include #include "GlobalTracking/MatchTPCITS.h" #include "GlobalTracking/MatchTPCITSParams.h" +#include "FT0Reconstruction/InteractionTag.h" #include "DataFormatsITSMFT/TopologyDictionary.h" #include "DataFormatsTPC/Constants.h" #include "Framework/DataProcessorSpec.h" @@ -80,6 +82,7 @@ class TPCITSMatchingDPL : public Task private: void updateTimeDependentParams(ProcessingContext& pc); + void storeConfigs(ProcessingContext& pc); std::shared_ptr mDataRequest; std::shared_ptr mGGCCDBRequest; o2::tpc::VDriftHelper mTPCVDriftHelper{}; @@ -112,6 +115,7 @@ void TPCITSMatchingDPL::run(ProcessingContext& pc) RecoContainer recoData; recoData.collectData(pc, *mDataRequest.get()); updateTimeDependentParams(pc); // Make sure this is called after recoData.collectData, which may load some conditions + storeConfigs(pc); static pmr::vector dummyMCLab, dummyMCLabAB; static pmr::vector> dummyCalib; @@ -125,15 +129,26 @@ void TPCITSMatchingDPL::run(ProcessingContext& pc) mMatching.run(recoData, matchedTracks, ABTrackletRefs, ABTrackletClusterIDs, matchLabels, ABTrackletLabels, calib); + mTimer.Stop(); +} + +void TPCITSMatchingDPL::storeConfigs(ProcessingContext& pc) +{ static bool first = true; if (first) { first = false; + const auto& confMatch = MatchTPCITSParams::Instance(); + const auto& confInt = ft0::InteractionTag::Instance(); if (pc.services().get().inputTimesliceId == 0) { - o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, MatchTPCITSParams::Instance().getName()), MatchTPCITSParams::Instance().getName()); + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, confMatch.getName()), confMatch.getName()); + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, confInt.getName()), confInt.getName()); + TMap md; + md.SetOwnerKeyValue(); + md.Add(new TObjString(confMatch.getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(confMatch.getName()).c_str())); + md.Add(new TObjString(confInt.getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(confInt.getName()).c_str())); + pc.outputs().snapshot(Output{"META", "TPCITSMATCHER", 0}, md); } } - - mTimer.Stop(); } void TPCITSMatchingDPL::endOfStream(EndOfStreamContext& ec) @@ -295,6 +310,9 @@ DataProcessorSpec getTPCITSMatchingSpec(GTrackID::mask_t src, bool useFT0, bool if (requestCTPLumi) { dataRequest->inputs.emplace_back("lumiCTP", o2::header::gDataOriginCTP, "LUMICTP", 0, Lifetime::Timeframe); } + + outputs.emplace_back("META", "TPCITSMATCHER", 0, Lifetime::Sporadic); + return DataProcessorSpec{ "itstpc-track-matcher", dataRequest->inputs, diff --git a/Detectors/GlobalTrackingWorkflow/study/src/TrackingStudy.cxx b/Detectors/GlobalTrackingWorkflow/study/src/TrackingStudy.cxx index 881ce9041ae04..38079d36a2e84 100644 --- a/Detectors/GlobalTrackingWorkflow/study/src/TrackingStudy.cxx +++ b/Detectors/GlobalTrackingWorkflow/study/src/TrackingStudy.cxx @@ -369,6 +369,9 @@ void TrackingStudySpec::process(o2::globaltracking::RecoContainer& recoData) if (iv != nv - 1) { auto& pve = pveVec[iv]; static_cast(pve) = pvvec[iv]; + if (mUseMC) { + pve.mcLb = recoData.getPrimaryVertexMCLabel(iv); + } // find best matching FT0 signal float bestTimeDiff = 1000, bestTime = -999; int bestFTID = -1; @@ -611,13 +614,16 @@ void TrackingStudySpec::process(o2::globaltracking::RecoContainer& recoData) vid[slot] = id; }; + std::vector pveT; // neighbours in time + std::vector pveZ; // neighbours in Z + std::vector idT(mMaxNeighbours), idZ(mMaxNeighbours); + std::vector dT(mMaxNeighbours), dZ(mMaxNeighbours); for (int cnt = 0; cnt < nvtot; cnt++) { + pveT.clear(); // neighbours in time + pveZ.clear(); // neighbours in Z + const auto& pve = pveVec[cnt]; float tv = pve.getTimeStamp().getTimeStamp(); - std::vector pveT(mMaxNeighbours); // neighbours in time - std::vector pveZ(mMaxNeighbours); // neighbours in Z - std::vector idT(mMaxNeighbours), idZ(mMaxNeighbours); - std::vector dT(mMaxNeighbours), dZ(mMaxNeighbours); for (int i = 0; i < mMaxNeighbours; i++) { idT[i] = idZ[i] = -1; dT[i] = mMaxVTTimeDiff; @@ -666,14 +672,14 @@ void TrackingStudySpec::process(o2::globaltracking::RecoContainer& recoData) } for (int i = 0; i < mMaxNeighbours; i++) { if (idT[i] != -1) { - pveT[i] = pveVec[idT[i]]; + pveT.push_back(pveVec[idT[i]]); } else { break; } } for (int i = 0; i < mMaxNeighbours; i++) { if (idZ[i] != -1) { - pveZ[i] = pveVec[idZ[i]]; + pveZ.push_back(pveVec[idZ[i]]); } else { break; } diff --git a/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/TrackerSpec.h b/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/TrackerSpec.h index 8ce63efcb7a3b..22a8092ec69c5 100644 --- a/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/TrackerSpec.h +++ b/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/TrackerSpec.h @@ -57,6 +57,7 @@ class TrackerDPL : public framework::Task private: void end(); void updateTimeDependentParams(framework::ProcessingContext& pc); + void storeConfigs(framework::ProcessingContext& pc); std::unique_ptr mRecChain = nullptr; std::unique_ptr mChainITS = nullptr; std::shared_ptr mGGCCDBRequest; diff --git a/Detectors/ITSMFT/ITS/workflow/src/TrackerSpec.cxx b/Detectors/ITSMFT/ITS/workflow/src/TrackerSpec.cxx index bbafc48e931ed..a198e638c5bac 100644 --- a/Detectors/ITSMFT/ITS/workflow/src/TrackerSpec.cxx +++ b/Detectors/ITSMFT/ITS/workflow/src/TrackerSpec.cxx @@ -10,7 +10,8 @@ // or submit itself to any jurisdiction. #include - +#include +#include #include "Framework/ControlService.h" #include "Framework/ConfigParamRegistry.h" #include "Framework/CCDBParamSpec.h" @@ -60,15 +61,27 @@ void TrackerDPL::run(ProcessingContext& pc) auto realt = mTimer.RealTime(); mTimer.Start(false); mITSTrackingInterface.updateTimeDependentParams(pc); + storeConfigs(pc); mITSTrackingInterface.run(pc); mTimer.Stop(); LOGP(info, "CPU Reconstruction time for this TF {:.2f} s (cpu), {:.2f} s (wall)", mTimer.CpuTime() - cput, mTimer.RealTime() - realt); +} + +void TrackerDPL::storeConfigs(ProcessingContext& pc) +{ static bool first = true; if (first) { first = false; if (pc.services().get().inputTimesliceId == 0) { - o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, o2::its::VertexerParamConfig::Instance().getName()), o2::its::VertexerParamConfig::Instance().getName()); - o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, o2::its::TrackerParamConfig::Instance().getName()), o2::its::TrackerParamConfig::Instance().getName()); + const auto& vtconf = o2::its::VertexerParamConfig::Instance(); + const auto& trconf = o2::its::TrackerParamConfig::Instance(); + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, vtconf.getName()), vtconf.getName()); + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, trconf.getName()), trconf.getName()); + TMap md; + md.SetOwnerKeyValue(); + md.Add(new TObjString(vtconf.getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(vtconf.getName()).c_str())); + md.Add(new TObjString(trconf.getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(trconf.getName()).c_str())); + pc.outputs().snapshot(Output{"META", "ITSTRACKER", 0}, md); } } } @@ -138,6 +151,7 @@ DataProcessorSpec getTrackerSpec(bool useMC, bool doStag, bool useGeom, int trgT outputs.emplace_back("ITS", "VERTICESMCPUR", 0, Lifetime::Timeframe); outputs.emplace_back("ITS", "TRACKSMCTR", 0, Lifetime::Timeframe); } + outputs.emplace_back("META", "ITSTRACKER", 0, Lifetime::Sporadic); return DataProcessorSpec{ .name = "its-tracker", diff --git a/Detectors/ITSMFT/MFT/workflow/include/MFTWorkflow/TrackerSpec.h b/Detectors/ITSMFT/MFT/workflow/include/MFTWorkflow/TrackerSpec.h index 8bd290caf5a41..3112e3efef5e6 100644 --- a/Detectors/ITSMFT/MFT/workflow/include/MFTWorkflow/TrackerSpec.h +++ b/Detectors/ITSMFT/MFT/workflow/include/MFTWorkflow/TrackerSpec.h @@ -44,6 +44,7 @@ class TrackerDPL : public o2::framework::Task private: void updateTimeDependentParams(framework::ProcessingContext& pc); + void storeConfigs(framework::ProcessingContext& pc); ///< MFT readout mode bool mMFTTriggered = false; ///< MFT readout is triggered diff --git a/Detectors/ITSMFT/MFT/workflow/src/TrackerSpec.cxx b/Detectors/ITSMFT/MFT/workflow/src/TrackerSpec.cxx index 6ceb04b3c4df6..e3bd557435ec0 100644 --- a/Detectors/ITSMFT/MFT/workflow/src/TrackerSpec.cxx +++ b/Detectors/ITSMFT/MFT/workflow/src/TrackerSpec.cxx @@ -18,7 +18,8 @@ #include "MFTTracking/Tracker.h" #include "MFTTracking/TrackCA.h" #include "MFTBase/GeometryTGeo.h" - +#include +#include #include #include @@ -64,6 +65,7 @@ void TrackerDPL::run(ProcessingContext& pc) mTimer[SWTot].Start(false); updateTimeDependentParams(pc); + storeConfigs(pc); gsl::span patterns = pc.inputs().get>("patterns"); auto compClusters = pc.inputs().get>("compClusters"); auto ntracks = 0; @@ -325,15 +327,23 @@ void TrackerDPL::run(ProcessingContext& pc) pc.outputs().snapshot(Output{"MFT", "TRACKSMCTR", 0}, allTrackLabels); } + mTimer[SWTot].Stop(); +} + +void TrackerDPL::storeConfigs(ProcessingContext& pc) +{ static bool first = true; if (first) { first = false; if (pc.services().get().inputTimesliceId == 0) { - o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, o2::mft::MFTTrackingParam::Instance().getName()), o2::mft::MFTTrackingParam::Instance().getName()); + const auto& conf = o2::mft::MFTTrackingParam::Instance(); + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, conf.getName()), conf.getName()); + TMap md; + md.SetOwnerKeyValue(); + md.Add(new TObjString(conf.getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(conf.getName()).c_str())); + pc.outputs().snapshot(Output{"META", "MFTTRACKER", 0}, md); } } - - mTimer[SWTot].Stop(); } void TrackerDPL::endOfStream(EndOfStreamContext& ec) @@ -462,6 +472,8 @@ DataProcessorSpec getTrackerSpec(bool useMC, bool useGeom, int nThreads) outputs.emplace_back("MFT", "TRACKSMCTR", 0, Lifetime::Timeframe); } + outputs.emplace_back("META", "MFTTRACKER", 0, Lifetime::Sporadic); + return DataProcessorSpec{ "mft-tracker", inputs, diff --git a/Detectors/ITSMFT/common/simulation/include/ITSMFTSimulation/Digitizer.h b/Detectors/ITSMFT/common/simulation/include/ITSMFTSimulation/Digitizer.h index c81e2d9476644..6c1caa4a0b1e9 100644 --- a/Detectors/ITSMFT/common/simulation/include/ITSMFTSimulation/Digitizer.h +++ b/Detectors/ITSMFT/common/simulation/include/ITSMFTSimulation/Digitizer.h @@ -127,7 +127,7 @@ class Digitizer : public TObject uint32_t mROFrameMin = 0; ///< lowest RO frame of current digits uint32_t mROFrameMax = 0; ///< highest RO frame of current digits uint32_t mNewROFrame = 0; ///< ROFrame corresponding to provided time - bool mIsBeforeFirstRO = false; + int mROFsWrtFirstRO = 0; uint32_t mEventROFrameMin = 0xffffffff; ///< lowest RO frame for processed events (w/o automatic noise ROFs) uint32_t mEventROFrameMax = 0; ///< highest RO frame forfor processed events (w/o automatic noise ROFs) diff --git a/Detectors/ITSMFT/common/simulation/src/Digitizer.cxx b/Detectors/ITSMFT/common/simulation/src/Digitizer.cxx index b1a92e988968b..5480392600074 100644 --- a/Detectors/ITSMFT/common/simulation/src/Digitizer.cxx +++ b/Detectors/ITSMFT/common/simulation/src/Digitizer.cxx @@ -164,15 +164,13 @@ void Digitizer::setEventTime(const o2::InteractionTimeRecord& irt, int layer) // we might get interactions to digitize from before // the first sampled IR + mROFsWrtFirstRO = std::floor(float(nbc) / mParams.getROFrameLengthInBC(layer)); if (nbc < 0) { - mNewROFrame = 0; - // this event is before the first RO - mIsBeforeFirstRO = true; + mNewROFrame = 0; // this event is before the first RO } else { mNewROFrame = nbc / mParams.getROFrameLengthInBC(layer); - mIsBeforeFirstRO = false; } - LOG(debug) << " NewROFrame " << mNewROFrame << " nbc " << nbc; + LOG(debug) << " NewROFrame " << mNewROFrame << " nbc " << nbc << " ROFsWrtFirstRO " << mROFsWrtFirstRO; // in continuous mode depends on starts of periodic readout frame mCollisionTimeWrtROF += (nbc % mParams.getROFrameLengthInBC(layer)) * o2::constants::lhc::LHCBunchSpacingNS; @@ -279,7 +277,7 @@ void Digitizer::processHit(const o2::itsmft::Hit& hit, uint32_t& maxFr, int evID if (isContinuous()) { timeInROF += mCollisionTimeWrtROF; } - if (mIsBeforeFirstRO && timeInROF < 0) { + if (mROFsWrtFirstRO < -1 || (mROFsWrtFirstRO == -1 && timeInROF < 0)) { // disregard this hit because it comes from an event before readout starts and it does not effect this RO return; } diff --git a/Detectors/MUON/MCH/Tracking/src/TrackFinderSpec.cxx b/Detectors/MUON/MCH/Tracking/src/TrackFinderSpec.cxx index 6239186309dc3..88202fc40a5ae 100644 --- a/Detectors/MUON/MCH/Tracking/src/TrackFinderSpec.cxx +++ b/Detectors/MUON/MCH/Tracking/src/TrackFinderSpec.cxx @@ -25,12 +25,15 @@ #include #include +#include +#include #include "Framework/CallbackService.h" #include "Framework/ConcreteDataMatcher.h" #include "Framework/ConfigParamRegistry.h" #include "Framework/ControlService.h" #include "Framework/DataProcessorSpec.h" +#include "Framework/DeviceSpec.h" #include "Framework/Lifetime.h" #include "Framework/Output.h" #include "Framework/Task.h" @@ -47,6 +50,7 @@ #include "DetectorsBase/Propagator.h" #include "MCHBase/Error.h" #include "MCHBase/ErrorMap.h" +#include "MCHBase/TrackerParam.h" #include "MCHTracking/TrackParam.h" #include "MCHTracking/Track.h" #include "MCHTracking/TrackFinder.h" @@ -130,6 +134,7 @@ class TrackFinderTask if (mCCDBRequest) { base::GRPGeomHelper::instance().checkUpdates(pc); } + storeConfigs(pc); uint32_t firstTForbit = pc.services().get().firstTForbit; @@ -268,6 +273,23 @@ class TrackFinderTask } } + //_________________________________________________________________________________________________ + void storeConfigs(ProcessingContext& pc) + { + static bool first = true; + if (first) { + first = false; + if (pc.services().get().inputTimesliceId == 0) { + const auto& conf = TrackerParam::Instance(); + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, conf.getName()), conf.getName()); + TMap md; + md.SetOwnerKeyValue(); + md.Add(new TObjString(conf.getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(conf.getName()).c_str())); + pc.outputs().snapshot(Output{"META", "MCHTRACKER", 0}, md); + } + } + } + bool mComputeTime = false; ///< compute the track time from the associated digits bool mDigits = false; ///< send to associated digits std::shared_ptr mCCDBRequest{}; ///< pointer to the CCDB requests @@ -296,6 +318,7 @@ o2::framework::DataProcessorSpec getTrackFinderSpec(const char* specName, bool c outputSpecs.emplace_back(OutputSpec{{"trackdigits"}, "MCH", "TRACKDIGITS", 0, Lifetime::Timeframe}); } outputSpecs.emplace_back(OutputSpec{{"trackerrors"}, "MCH", "TRACKERRORS", 0, Lifetime::Timeframe}); + outputSpecs.emplace_back("META", "MCHTRACKER", 0, Lifetime::Sporadic); auto ccdbRequest = disableCCDBMagField ? nullptr : std::make_shared(false, // orbitResetTime diff --git a/Detectors/MUON/MID/Workflow/src/TrackerSpec.cxx b/Detectors/MUON/MID/Workflow/src/TrackerSpec.cxx index 28536bdbf570e..5b6ea6002ca33 100644 --- a/Detectors/MUON/MID/Workflow/src/TrackerSpec.cxx +++ b/Detectors/MUON/MID/Workflow/src/TrackerSpec.cxx @@ -17,6 +17,8 @@ #include "MIDWorkflow/TrackerSpec.h" #include +#include +#include #include "Framework/DataRefUtils.h" #include "Framework/CCDBParamSpec.h" #include "Framework/ConfigParamRegistry.h" @@ -24,6 +26,7 @@ #include "Framework/Logger.h" #include "Framework/Output.h" #include "Framework/Task.h" +#include "Framework/DeviceSpec.h" #include "DataFormatsMID/Cluster.h" #include "DataFormatsMID/ROFRecord.h" #include "DataFormatsMID/Track.h" @@ -31,6 +34,7 @@ #include "DetectorsBase/GeometryManager.h" #include "MIDTracking/HitMapBuilder.h" #include "MIDTracking/Tracker.h" +#include "MIDTracking/TrackerParam.h" #include "MIDSimulation/TrackLabeler.h" #include "CommonUtils/NameConf.h" #include "DetectorsBase/GRPGeomHelper.h" @@ -63,6 +67,7 @@ class TrackerDeviceDPL { auto tStart = std::chrono::high_resolution_clock::now(); updateTimeDependentParams(pc); + storeConfigs(pc); auto clusters = pc.inputs().get>("mid_clusters"); @@ -142,6 +147,22 @@ class TrackerDeviceDPL pc.inputs().get*>("mid_rejectlist_forTracks"); } + void storeConfigs(of::ProcessingContext& pc) + { + static bool first = true; + if (first) { + first = false; + if (pc.services().get().inputTimesliceId == 0) { + const auto& conf = TrackerParam::Instance(); + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, conf.getName()), conf.getName()); + TMap md; + md.SetOwnerKeyValue(); + md.Add(new TObjString(conf.getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(conf.getName()).c_str())); + pc.outputs().snapshot(of::Output{"META", "MIDTRACKER", 0}, md); + } + } + } + bool mIsMC = false; bool mKeepAll = false; bool mCheckMasked = false; @@ -184,6 +205,7 @@ framework::DataProcessorSpec getTrackerSpec(bool isMC, bool checkMasked) outputSpecs.emplace_back(of::OutputSpec{header::gDataOriginMID, "TRACKLABELS"}); outputSpecs.emplace_back(of::OutputSpec{header::gDataOriginMID, "TRCLUSLABELS"}); } + outputSpecs.emplace_back("META", "MIDTRACKER", 0, of::Lifetime::Sporadic); return of::DataProcessorSpec{ "MIDTracker", diff --git a/Detectors/O2TrivialMC/src/O2TrivialMCLinkDef.h b/Detectors/O2TrivialMC/src/O2TrivialMCLinkDef.h index c85dd2d378ccb..51c7e396a49b7 100644 --- a/Detectors/O2TrivialMC/src/O2TrivialMCLinkDef.h +++ b/Detectors/O2TrivialMC/src/O2TrivialMCLinkDef.h @@ -15,4 +15,7 @@ #pragma link off all classes; #pragma link off all functions; +#pragma link C++ class o2::mc::O2TrivialMCEngine + ; +#pragma link C++ class o2::mc::O2TrivialMCApplication + ; + #endif diff --git a/Detectors/PHOS/calib/src/PHOSRunbyrunCalibrator.cxx b/Detectors/PHOS/calib/src/PHOSRunbyrunCalibrator.cxx index 63e51f06c0e64..0c4e740168ffe 100644 --- a/Detectors/PHOS/calib/src/PHOSRunbyrunCalibrator.cxx +++ b/Detectors/PHOS/calib/src/PHOSRunbyrunCalibrator.cxx @@ -213,9 +213,9 @@ void PHOSRunbyrunCalibrator::writeHistos() { // Merge collected in different slots histograms - TF1 fRatio("ratio", this, &PHOSRunbyrunCalibrator::CBRatio, 0, 1, 6, "PHOSRunbyrunCalibrator", "CBRatio"); - TF1 fBg("background", this, &PHOSRunbyrunCalibrator::bg, 0, 1, 6, "PHOSRunbyrunCalibrator", "bg"); - TF1 fSignal("signal", this, &PHOSRunbyrunCalibrator::CBSignal, 0, 1, 6, "PHOSRunbyrunCalibrator", "CBSignal"); + TF1 fRatio("ratio", this, &PHOSRunbyrunCalibrator::CBRatio, 0, 1, 6); + TF1 fBg("background", this, &PHOSRunbyrunCalibrator::bg, 0, 1, 6); + TF1 fSignal("signal", this, &PHOSRunbyrunCalibrator::CBSignal, 0, 1, 6); // fit inv mass distributions for (int mod = 0; mod < 4; mod++) { diff --git a/Detectors/Passive/CMakeLists.txt b/Detectors/Passive/CMakeLists.txt index a24954ad10539..e3bc45f8e8153 100644 --- a/Detectors/Passive/CMakeLists.txt +++ b/Detectors/Passive/CMakeLists.txt @@ -24,7 +24,8 @@ o2_add_library(DetectorsPassive src/HallSimParam.cxx src/PassiveBase.cxx src/ExternalModule.cxx - PUBLIC_LINK_LIBRARIES O2::Field O2::DetectorsBase O2::SimConfig) + PUBLIC_LINK_LIBRARIES O2::Field O2::DetectorsBase O2::SimConfig + RapidJSON::RapidJSON) o2_target_root_dictionary(DetectorsPassive HEADERS include/DetectorsPassive/Absorber.h diff --git a/Detectors/Passive/include/DetectorsPassive/ExternalModule.h b/Detectors/Passive/include/DetectorsPassive/ExternalModule.h index 155870ae42a6d..01c6dfea16947 100644 --- a/Detectors/Passive/include/DetectorsPassive/ExternalModule.h +++ b/Detectors/Passive/include/DetectorsPassive/ExternalModule.h @@ -14,6 +14,8 @@ #include "DetectorsPassive/PassiveBase.h" // base class of passive modules #include "Rtypes.h" // for Pipe::Class, ClassDef, Pipe::Streamer +#include +#include class TGeoVolume; class TGeoTransformation; @@ -41,22 +43,23 @@ class ExternalModule : public PassiveBase ~ExternalModule() override = default; void ConstructGeometry() override; + /// Build a list of external (passive) modules from a JSON description file. + /// The file must contain an "externalModules" array; each entry needs at least + /// "name", "macro" and "anchor"; an optional "placement" object may carry + /// "translation":[x,y,z] (cm) and "rotation_deg":[rx,ry,rz] (degrees). + /// Ownership of the returned modules is transferred to the caller. + static std::vector createFromJSON(const std::string& jsonfile); + /// Clone this object (used in MT mode only) FairModule* CloneModule() const override { return nullptr; } - typedef std::function GeomBuilderFcn; // function hook for external geometry builder - private: // void createMaterials(); ExternalModule(const ExternalModule& orig); ExternalModule& operator=(const ExternalModule&); - GeomBuilderFcn mGeomHook; ExternalModuleOptions mOptions; - bool initGeomBuilderHook(); // function to load/JIT Geometry builder hook - void remapMedia(TGeoVolume* vol); // performs a remapping of materials/media IDs after registration with VMC - // ClassDefOverride(ExternalModule, 0); }; } // namespace passive diff --git a/Detectors/Passive/src/ExternalModule.cxx b/Detectors/Passive/src/ExternalModule.cxx index fc6bd6953b82d..ebfa405d877de 100644 --- a/Detectors/Passive/src/ExternalModule.cxx +++ b/Detectors/Passive/src/ExternalModule.cxx @@ -12,16 +12,15 @@ // Sandro Wenzel (CERN), 2026 #include -#include -#include +#include +#include #include #include #include -#include -#include -#include -#include -#include +#include +#include +#include +#include // ClassImp(o2::passive::ExternalModule) @@ -32,121 +31,17 @@ ExternalModule::ExternalModule(const char* name, const char* long_title, Externa { } -void ExternalModule::remapMedia(TGeoVolume* top_volume) -{ - std::unordered_map medium_ptr_mapping; - std::unordered_set volumes_already_treated; - int counter = 1; - - auto modulename = GetName(); - - // The transformer function - auto transform_media = [&](TGeoVolume* vol_) { - if (volumes_already_treated.find(vol_) != volumes_already_treated.end()) { - // this volume was already transformed - return; - } - volumes_already_treated.insert(vol_); - - if (dynamic_cast(vol_)) { - // do nothing for assemblies (they don't have a medium) - return; - } - - auto medium = vol_->GetMedium(); - if (!medium) { - return; - } - - auto iter = medium_ptr_mapping.find(medium); - if (iter != medium_ptr_mapping.end()) { - // This medium has already been transformed, so - // we just update the volume - vol_->SetMedium(iter->second); - return; - } else { - std::cout << "Transforming media with name " << medium->GetName() << " for volume " << vol_->GetName() << "\n"; - - // we found a medium, not yet treated - auto curr_mat = medium->GetMaterial(); - auto& matmgr = o2::base::MaterialManager::Instance(); - - matmgr.Material(modulename, counter, curr_mat->GetName(), curr_mat->GetA(), curr_mat->GetZ(), curr_mat->GetDensity(), curr_mat->GetRadLen(), curr_mat->GetIntLen()); - // TGeo medium params are stored in a flat array with the following convention - // fParams[0] = isvol; - // fParams[1] = ifield; - // fParams[2] = fieldm; - // fParams[3] = tmaxfd; - // fParams[4] = stemax; - // fParams[5] = deemax; - // fParams[6] = epsil; - // fParams[7] = stmin; - const auto isvol = medium->GetParam(0); - const auto isxfld = medium->GetParam(1); - const auto sxmgmx = medium->GetParam(2); - const auto tmaxfd = medium->GetParam(3); - const auto stemax = medium->GetParam(4); - const auto deemax = medium->GetParam(5); - const auto epsil = medium->GetParam(6); - const auto stmin = medium->GetParam(7); - - matmgr.Medium(modulename, counter, medium->GetName(), counter, isvol, isxfld, sxmgmx, tmaxfd, stemax, deemax, epsil, stmin); - - // there will be new Material and Medium objects; fetch them - auto new_med = matmgr.getTGeoMedium(modulename, counter); - - // insert into cache - medium_ptr_mapping[medium] = new_med; - vol_->SetMedium(new_med); - counter++; - } - }; // end transformer lambda - - // a generic volume walker - std::function visit_volume; - visit_volume = [&](TGeoVolume* vol) -> void { - if (!vol) { - return; - } - - // call the transformer - transform_media(vol); - - // Recurse into daughters - const int nd = vol->GetNdaughters(); - for (int i = 0; i < nd; ++i) { - TGeoNode* node = vol->GetNode(i); - if (!node) { - continue; - } - TGeoVolume* child = node->GetVolume(); - if (!child) { - continue; - } - - visit_volume(child); - } - }; - - visit_volume(top_volume); -} - void ExternalModule::ConstructGeometry() { - // JIT the geom builder hook - if (!initGeomBuilderHook()) { - LOG(error) << " Could not load geometry builder hook"; - return; - } - - // otherwise execute it and obtain pointer to top most module volume - auto module_top = mGeomHook(); + // JIT the geom builder macro and obtain the top most module volume + auto module_top = o2::base::buildCADVolumeFromMacro(mOptions.root_macro_file, GetName()); if (!module_top) { - LOG(error) << "No module found\n"; + LOG(error) << "No module geometry could be built from " << mOptions.root_macro_file; return; } - remapMedia(const_cast(module_top)); + // bring the CAD media under O2's MaterialManager + o2::base::remapCADMedia(module_top, GetName()); // place it into the provided anchor volume (needs to exist) auto anchor = gGeoManager->FindVolumeFast(mOptions.anchor_volume.c_str()); @@ -154,22 +49,101 @@ void ExternalModule::ConstructGeometry() LOG(error) << "Anchor volume " << mOptions.anchor_volume << " not found. Aborting"; return; } - anchor->AddNode(const_cast(module_top), 1, const_cast(mOptions.placement)); + anchor->AddNode(module_top, 1, const_cast(mOptions.placement)); +} + +namespace +{ +// Build a TGeoCombiTrans from an optional JSON "placement" object carrying +// "translation":[x,y,z] (cm) and/or "rotation_deg":[rx,ry,rz] (deg, applied X,Y,Z). +TGeoMatrix* makePlacementFromJSON(const rapidjson::Value& placement) +{ + auto combi = new TGeoCombiTrans(); + if (placement.HasMember("rotation_deg") && placement["rotation_deg"].IsArray()) { + const auto& r = placement["rotation_deg"]; + if (r.Size() == 3) { + combi->RotateX(r[0].GetDouble()); + combi->RotateY(r[1].GetDouble()); + combi->RotateZ(r[2].GetDouble()); + } else { + LOG(warning) << "ExternalModule placement 'rotation_deg' must have 3 entries; ignoring"; + } + } + if (placement.HasMember("translation") && placement["translation"].IsArray()) { + const auto& t = placement["translation"]; + if (t.Size() == 3) { + combi->SetDx(t[0].GetDouble()); + combi->SetDy(t[1].GetDouble()); + combi->SetDz(t[2].GetDouble()); + } else { + LOG(warning) << "ExternalModule placement 'translation' must have 3 entries; ignoring"; + } + } + return combi; } +} // namespace -bool ExternalModule::initGeomBuilderHook() +std::vector ExternalModule::createFromJSON(const std::string& jsonfile) { - if (mOptions.root_macro_file.size() > 0) { - LOG(info) << "Initializing the hook for geometry module building"; - auto expandedHookFileName = o2::utils::expandShellVarsInFileName(mOptions.root_macro_file); - if (std::filesystem::exists(expandedHookFileName)) { - // if this file exists we will compile the hook on the fly (the last one is an identifier --> maybe make it dependent on this class) - mGeomHook = o2::conf::GetFromMacro(mOptions.root_macro_file, "get_builder_hook_unchecked()", "function", "o2_passive_extmodule_builder"); - LOG(info) << "Hook initialized from file " << expandedHookFileName; - return true; + std::vector result; + + auto expanded = o2::utils::expandShellVarsInFileName(jsonfile); + std::ifstream fileStream(expanded, std::ios::in); + if (!fileStream.is_open()) { + LOG(error) << "Cannot open external geometry config file '" << expanded << "'"; + return result; + } + + rapidjson::IStreamWrapper isw(fileStream); + rapidjson::Document doc; + doc.ParseStream(isw); + if (doc.HasParseError()) { + LOG(error) << "Error parsing external geometry JSON '" << expanded << "': " + << rapidjson::GetParseError_En(doc.GetParseError()) + << " (offset " << doc.GetErrorOffset() << ")"; + return result; + } + if (!doc.HasMember("externalModules") || !doc["externalModules"].IsArray()) { + LOG(error) << "External geometry JSON '" << expanded << "' must contain an 'externalModules' array"; + return result; + } + + auto getString = [](const rapidjson::Value& v, const char* key) -> std::string { + if (v.HasMember(key) && v[key].IsString()) { + return v[key].GetString(); + } + return std::string(); + }; + + for (const auto& entry : doc["externalModules"].GetArray()) { + if (!entry.IsObject()) { + LOG(error) << "Skipping non-object entry in 'externalModules'"; + continue; + } + const auto name = getString(entry, "name"); + if (name.empty()) { + LOG(error) << "Skipping external module entry without 'name'"; + continue; + } + ExternalModuleOptions options; + options.root_macro_file = getString(entry, "macro"); + options.anchor_volume = getString(entry, "anchor"); + if (options.root_macro_file.empty() || options.anchor_volume.empty()) { + LOG(error) << "External module '" << name << "' requires both 'macro' and 'anchor'; skipping"; + continue; + } + if (entry.HasMember("placement") && entry["placement"].IsObject()) { + options.placement = makePlacementFromJSON(entry["placement"]); + } + auto title = getString(entry, "title"); + if (title.empty()) { + title = name; } + LOG(info) << "Configured external module '" << name << "' from macro '" << options.root_macro_file + << "' anchored to volume '" << options.anchor_volume << "'"; + result.push_back(new ExternalModule(name.c_str(), title.c_str(), options)); } - return false; + return result; } } // namespace o2::passive \ No newline at end of file diff --git a/Detectors/TPC/calibration/SpacePoints/CMakeLists.txt b/Detectors/TPC/calibration/SpacePoints/CMakeLists.txt index 47bb9c09a9951..ac33a0b632dab 100644 --- a/Detectors/TPC/calibration/SpacePoints/CMakeLists.txt +++ b/Detectors/TPC/calibration/SpacePoints/CMakeLists.txt @@ -43,9 +43,40 @@ o2_add_test_root_macro(macro/staticMapCreator.C PUBLIC_LINK_LIBRARIES O2::SpacePoints LABELS tpc COMPILE_ONLY) +o2_add_test_root_macro(macro/staticMapCreatorCPM.C + PUBLIC_LINK_LIBRARIES O2::SpacePoints + O2::CCDB + O2::Algorithm + O2::Framework + O2::CommonConstants + O2::DataFormatsParameters + O2::ReconstructionDataFormats + O2::DetectorsBase + LABELS tpc COMPILE_ONLY) + +o2_add_test_root_macro(macro/SmoothingExtrapolate.C + PUBLIC_LINK_LIBRARIES O2::SpacePoints + O2::Algorithm + O2::TPCCalibration + O2::CCDB + O2::TPCBaseRecSim + LABELS tpc COMPILE_ONLY) + +o2_add_test_root_macro(macro/voxResQA.C + PUBLIC_LINK_LIBRARIES O2::SpacePoints + O2::TPCBaseRecSim + O2::CommonUtils + O2::Framework + LABELS tpc COMPILE_ONLY) + install(FILES macro/staticMapCreator.C DESTINATION share/macro/) +install(FILES macro/staticMapCreatorCPM.C + macro/SmoothingExtrapolate.C + macro/voxResQA.C + DESTINATION share/macro/) + o2_add_test(TrackResiduals COMPONENT_NAME calibration PUBLIC_LINK_LIBRARIES O2::SpacePoints diff --git a/Detectors/TPC/calibration/SpacePoints/macro/SmoothingExtrapolate.C b/Detectors/TPC/calibration/SpacePoints/macro/SmoothingExtrapolate.C new file mode 100644 index 0000000000000..2af42e68898ea --- /dev/null +++ b/Detectors/TPC/calibration/SpacePoints/macro/SmoothingExtrapolate.C @@ -0,0 +1,1002 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#if !defined(__CLING__) || defined(__ROOTCLING__) + +#include +#include +#include +#include "TFile.h" +#include "TSystem.h" +#include "TTree.h" +#include "TF1.h" +#include "TGraph.h" +#include "TProfile.h" +#include "Math/MinimizerOptions.h" + +#include "Algorithm/RangeTokenizer.h" +#include "SpacePoints/TrackResiduals.h" +#include "TPCCalibration/TPCFastSpaceChargeCorrectionHelper.h" +#include "CCDB/BasicCCDBManager.h" +#include "TPCCalibration/TPCScaler.h" +#if __has_include("TPCBaseRecSim/CDBTypes.h") +#include "TPCBaseRecSim/CDBTypes.h" +#else +#include "TPCBase/CDBTypes.h" +#endif + +#endif + +using namespace o2::tpc; +using namespace o2::gpu; + +//------------------------------------------------------------------------------------------------------------ +static const Float_t RowX[153] = + { + 85.225, 85.975, 86.725, 87.475, 88.225, 88.975, 89.725, 90.475, 91.225, 91.975, 92.725, 93.475, 94.225, 94.975, 95.725, 96.475, + 97.225, 97.975, 98.725, 99.475, 100.225, 100.975, 101.725, 102.475, 103.225, 103.975, 104.725, 105.475, 106.225, 106.975, 107.725, + 108.475, 109.225, 109.975, 110.725, 111.475, 112.225, 112.975, 113.725, 114.475, 115.225, 115.975, 116.725, 117.475, 118.225, 118.975, + 119.725, 120.475, 121.225, 121.975, 122.725, 123.475, 124.225, 124.975, 125.725, 126.475, 127.225, 127.975, 128.725, 129.475, 130.225, + 130.975, 131.725, 135.200, 136.200, 137.200, 138.200, 139.200, 140.200, 141.200, 142.200, 143.200, 144.200, 145.200, 146.200, 147.200, + 148.200, 149.200, 150.200, 151.200, 152.200, 153.200, 154.200, 155.200, 156.200, 157.200, 158.200, 159.200, 160.200, 161.200, 162.200, + 163.200, 164.200, 165.200, 166.200, 167.200, 168.200, 171.400, 172.600, 173.800, 175.000, 176.200, 177.400, 178.600, 179.800, 181.000, + 182.200, 183.400, 184.600, 185.800, 187.000, 188.200, 189.400, 190.600, 191.800, 193.000, 194.200, 195.400, 196.600, 197.800, 199.000, + 200.200, 201.400, 202.600, 203.800, 205.000, 206.200, 209.650, 211.150, 212.650, 214.150, 215.650, 217.150, 218.650, 220.150, 221.650, + 223.150, 224.650, 226.150, 227.650, 229.150, 230.650, 232.150, 233.650, 235.150, 236.650, 238.150, 239.650, 241.150, 242.650, 244.150, + 245.650, 246.650}; // last value added +//------------------------------------------------------------------------------------------------------------ + +//---------------------------------------------------------------------------------------- +Double_t PolyFitFunc(Double_t* x_val, Double_t* par) +{ + Double_t x, y, par0, par1, par2, par3, par4, par5; + par0 = par[0]; + par1 = par[1]; + par2 = par[2]; + par3 = par[3]; + par4 = par[4]; + par5 = par[5]; + x = x_val[0]; + y = par0 + par1 * x + par2 * x * x + par3 * x * x * x + par4 * x * x * x * x + par5 * x * x * x * x * x; + return y; +} +//---------------------------------------------------------------------------------------- + +// Function to create Gaussian filter +void vec_FilterCreation(std::vector>>& vec_GKernel, Int_t Delta_X, Int_t Delta_Y, Int_t Delta_Z, Double_t sigma) +{ + // initialising standard deviation to 1.0 + // double sigma = 1.0; + double r, s = 2.0 * sigma * sigma; + + // sum is for normalization + double sum = 0.0; + + // generating 5x5 kernel + for (int x = -Delta_X; x <= Delta_X; x++) { + for (int y = -Delta_Y; y <= Delta_Y; y++) { + for (int z = -Delta_Z; z <= Delta_Z; z++) { + r = sqrt(x * x + y * y + z * z); + vec_GKernel[x + Delta_X][y + Delta_Y][z + Delta_Z] = (exp(-(r * r) / s)) / (M_PI * s); + sum += vec_GKernel[x + Delta_X][y + Delta_Y][z + Delta_Z]; + } + } + } + + // normalising the Kernel + for (int i = 0; i < (Delta_X * 2 + 1); ++i) { + for (int j = 0; j < (Delta_Y * 2 + 1); ++j) { + for (int k = 0; k < (Delta_Z * 2 + 1); ++k) { + vec_GKernel[i][j][k] /= sum; + } + } + } +} + +// Mean TPC scaler (IDC) values for one timestamp, from the standard CCDB CalScaler/CalScalerWeights +// objects. Used for the offline IDC join below -- see the "Offline IDC join" block in +// SmoothingExtrapolate() for why this is done here rather than during map creation. +bool getScalerValues(o2::ccdb::BasicCCDBManager& ccdbmgr, long tfTimeInMS, float& scA, float& scC) +{ + auto* scalerTree = ccdbmgr.getForTimeStamp(o2::tpc::CDBTypeMap.at(o2::tpc::CDBType::CalScaler), long(std::ceil(tfTimeInMS))); + auto* scalerWeights = ccdbmgr.getForTimeStamp(o2::tpc::CDBTypeMap.at(o2::tpc::CDBType::CalScalerWeights), long(std::ceil(tfTimeInMS))); + + if (!scalerTree) { + LOGP(error, "Could not get 'TPC/Calib/Scaler' for time stamp {}", tfTimeInMS); + return false; + } + // The caller sets setFatalWhenNull(false), so a missing object comes back as nullptr rather than + // aborting -- the weights have to be checked before being dereferenced below. + if (!scalerWeights) { + LOGP(error, "Could not get 'TPC/Calib/ScalerWeights' for time stamp {}", tfTimeInMS); + return false; + } + + o2::tpc::TPCScaler scaler; + scaler.setFromTree(*(scalerTree)); + scaler.setScalerWeights(*scalerWeights); + scaler.useWeights(true); + scaler.setIonDriftTimeMS(500); + + static bool defaultScalerReported = false; + static bool badScalerValueReported = false; + if (scaler.getRun() == 0) { + if (!defaultScalerReported) { + LOGP(error, "Retrieved default scaler entry 'TPC/Calib/Scaler' for time stamp {}", tfTimeInMS); + defaultScalerReported = true; + } + return false; + } + + scA = scaler.getMeanScaler(tfTimeInMS, o2::tpc::Side::A); + scC = scaler.getMeanScaler(tfTimeInMS, o2::tpc::Side::C); + + if ((scA <= 0) || (scC <= 0)) { + if (!badScalerValueReported) { + LOGP(error, "Bad scaler value, first seen for time stamp {}, scA: {}, scC: {}", tfTimeInMS, scA, scC); + badScalerValueReported = true; + } + scA = 0; + scC = 0; + return false; + } + + return true; +} + +void SmoothingExtrapolate(const char* fileName = "debugVoxRes.root", TString fileOutName = "SmoothVoxRes.root", + int do_smoothing = 1, int do_extrapolation = 2, + int A11maxZ2X = -1, bool maskIA11 = false, + Int_t N_bins_X_GF = 1, Int_t N_bins_Y_GF = 2, + Int_t N_bins_Z_GF = 0, Float_t sigma_GF = 1.2) +{ + + // do_extrapolation: + // 0 -> no extrapolation to low radii done + // 1 -> only smoothed values are extrapolated + // 2 -> smoothed and raw values are extrapolated + // 3 -> only raw values are extrapolated + + // Example, smoothing and extrapolating both smoothed and raw values: + // SmoothingExtrapolate("voxRes.__.it0.root", "voxRes._smooth.root", 1, 2, -1, false, 1, 2, 0, 1.2) + + const float maxDeltaCut = 25.0; // maximum value for any Delta to be accepted + const float min_statistics = 20; + const float max_extrapolation_value = 20.0; // maximum value for extrapolation in DX, DY, DZ + //---------------------------------------------------------------- + // input + if (gSystem->AccessPathName(fileName)) { + LOGP(error, "input file {} does not exist", fileName); + return; + } + + auto file = std::unique_ptr(TFile::Open(fileName, "READ")); + if (!file || !file->IsOpen()) { + LOGP(error, "input file {} does not exist", fileName); + return; + } + + TTree* voxResTree = nullptr; + file->cd(); + gDirectory->GetObject("voxResTree", voxResTree); + if (!voxResTree) { + LOGP(error, "tree voxResTree does not exist in {}", fileName); + return; + } + + o2::tpc::TrackResiduals::VoxRes* voxRes_map = nullptr; + Long64_t entries_input_map = voxResTree->GetEntries(); + LOGP(info, "entries_input_map: {}", entries_input_map); + voxResTree->SetBranchAddress("voxRes", &voxRes_map); + + // required for the binning that was used + auto userInfo = voxResTree->GetUserInfo(); + if (!userInfo->FindObject("y2xBinning") || !userInfo->FindObject("z2xBinning")) { + LOGP(error, "'y2xBinning' or 'z2xBinning' not found in UserInfo, but required to get the correct binning"); + return; + } + + // Obtain configuration + const SpacePointsCalibConfParam& params = SpacePointsCalibConfParam::Instance(); + if (std::filesystem::exists("scdconfig.ini")) { + params.updateFromFile("scdconfig.ini"); + } + // TrackResiduals::setZ2XBinning() (called below) reads scdcalib.maxZ2X directly and uses it to scale + // the physical z/x bin boundaries -- it is baked into what each z2x voxel index in the input tree + // actually means, not a cosmetic knob. There is no scdconfig.ini on the GRID, so without this the + // code default (1.0) would silently apply instead of whatever stage 1 actually used (production 1.4), + // misaligning this macro's re-derived binning against the tree's real geometry. Must happen BEFORE + // setZ2XBinning() below. See staticMapCreatorCPM.C's UserInfo::Add("maxZ2X", ...) for where this comes + // from. + if (auto* maxZ2XObj = userInfo->FindObject("maxZ2X")) { + const std::string maxZ2XStr = maxZ2XObj->GetTitle(); + o2::conf::ConfigurableParam::setValue("scdcalib.maxZ2X", maxZ2XStr); + LOGP(info, "Set scdcalib.maxZ2X = {} from input UserInfo (matches stage 1)", maxZ2XStr); + } else { + LOGP(warning, + "'maxZ2X' not found in input UserInfo (older input file?) -- using scdcalib.maxZ2X = {} " + "(scdconfig.ini/code default), which may NOT match the value stage 1 actually used to " + "build this tree's z2x binning!", + params.maxZ2X); + } + + LOGP(info, "----- Dumping configuration values START -----"); + params.printKeyValues(); + LOGP(info, "----- Dumping configuration values END -----"); + + LOGP(info, "Get binning from userInfo"); + o2::tpc::TrackResiduals trackResiduals; + auto y2xBins = o2::RangeTokenizer::tokenize(userInfo->FindObject("y2xBinning")->GetTitle()); + auto z2xBins = o2::RangeTokenizer::tokenize(userInfo->FindObject("z2xBinning")->GetTitle()); + trackResiduals.setY2XBinning(y2xBins); + trackResiduals.setZ2XBinning(z2xBins); + trackResiduals.init(); + LOGP(info, "trackResiduals initialized"); + //---------------------------------------------------------------- + + //---------------------------------------------------------------- + // Offline IDC join: staticMapCreatorCPM.C on the GRID intentionally does not access IDCs + // live (useCTPLumi=2 -- CCDB access from inside the hot per-TF loop was slow/unreliable), so + // meanIDC/medianIDC in the input's UserInfo are placeholder 0. Per-TF timestamps are recorded + // regardless (the "OrbitLumiInfo" tree's timeMSsel branch) specifically so this can be joined back + // offline, with a properly-cached CCDB client. Timestamps are sorted before querying: + // TPCScaler/TPCScalerWeights CCDB objects are valid over a time range, and BasicCCDBManager's cache + // (setCaching(true)) only hits when consecutive queries land in the same validity window -- + // unsorted access would bounce between windows and force a real CCDB fetch almost every call. + float meanIDCReal = 0.f; + float medianIDCReal = 0.f; + { + TTree* orbitLumiTree = nullptr; + file->GetObject("OrbitLumiInfo", orbitLumiTree); + if (!orbitLumiTree || orbitLumiTree->GetEntries() == 0) { + LOGP(warning, "Offline IDC join: no 'OrbitLumiInfo' tree (or it's empty) in the input file -- meanIDC/medianIDC stay 0"); + } else { + std::vector* timeMSselPtr = nullptr; + orbitLumiTree->SetBranchAddress("timeMSsel", &timeMSselPtr); + orbitLumiTree->GetEntry(0); + if (!timeMSselPtr || timeMSselPtr->empty()) { + LOGP(warning, "Offline IDC join: 'timeMSsel' branch missing or empty -- meanIDC/medianIDC stay 0"); + } else { + std::vector sortedTimes(*timeMSselPtr); + std::sort(sortedTimes.begin(), sortedTimes.end()); + + auto& ccdbmgr = o2::ccdb::BasicCCDBManager::instance(); + ccdbmgr.setCaching(true); + ccdbmgr.setFatalWhenNull(false); + ccdbmgr.setURL("http://alice-ccdb.cern.ch"); + + std::vector averageIDCs; + averageIDCs.reserve(sortedTimes.size()); + for (long tfTimeInMS : sortedTimes) { + float scA = 0.f, scC = 0.f; + if (getScalerValues(ccdbmgr, tfTimeInMS, scA, scC)) { + averageIDCs.emplace_back((scA + scC) / 2.f); + } + } + if (averageIDCs.empty()) { + LOGP(warning, "Offline IDC join: no valid scaler values found for any of {} TFs -- meanIDC/medianIDC stay 0", sortedTimes.size()); + } else { + double sum = 0.0; + for (float v : averageIDCs) { + sum += v; + } + meanIDCReal = static_cast(sum / averageIDCs.size()); + medianIDCReal = static_cast(TMath::Median(static_cast(averageIDCs.size()), averageIDCs.data())); + LOGP(info, "Offline IDC join: {} of {} TFs gave a valid scaler value, meanIDC={}, medianIDC={}", + averageIDCs.size(), sortedTimes.size(), meanIDCReal, medianIDCReal); + } + } + } + } + //---------------------------------------------------------------- + + //---------------------------------------------------------------- + // Get voxel map binning from map + + const int nXBins = trackResiduals.getNXBins(); + const int nY2XBins = trackResiduals.getNY2XBins(); + const int nZ2XBins = trackResiduals.getNZ2XBins(); + LOGP(info, "binning X,Y2X,Z2X: {}, {}, {}", nXBins, nY2XBins, nZ2XBins); + //---------------------------------------------------------------- + + //---------------------------------------------------------------- + // output + // Create a new file + a clone of old tree in new file + TFile* outputfile = new TFile(fileOutName.Data(), "RECREATE"); + LOGP(info, "Output file: {} created", fileOutName.Data()); + + o2::tpc::TrackResiduals::VoxRes mVoxelResultsOut{}; ///< the results from mVoxelResults are copied in here to be able to stream them + o2::tpc::TrackResiduals::VoxRes* mVoxelResultsOutPtr{&mVoxelResultsOut}; ///< pointer to set the branch address to for the output + std::unique_ptr mTreeOut; + //---------------------------------------------------------------- + + //---------------------------------------------------------------- + ROOT::Math::MinimizerOptions::SetDefaultMinimizer("GSLSimAn"); + + TF1* func_PolyFitFunc = new TF1("func_PolyFitFunc", PolyFitFunc, 0, 150, 6); + TF1* func_PolyFitFunc_raw = new TF1("func_PolyFitFunc_raw", PolyFitFunc, 0, 150, 6); + TProfile* tp_DX_vs_X_raw = new TProfile("tp_DX_vs_X_raw", "tp_DX_vs_X_raw", 250, 0, 250); + TProfile* tp_Stat_vs_X = new TProfile("tp_Stat_vs_X", "tp_Stat_vs_X;row;", 250, 0, 250); + TProfile* tp_Stat_vs_X_single = new TProfile("tp_Stat_vs_X_single", "tp_Stat_vs_X;row;", 250, 0, 250); + TProfile* tp_DX_vs_X_single = new TProfile("tp_DX_vs_X_single", "tp_DX_vs_X;row;", 250, 0, 250); + TGraph* tg_Stat_vs_X_slice = new TGraph(); + TProfile* tp_DX_vs_Row_raw = new TProfile("tp_DX_vs_Row_raw", "tp_DX_vs_Row_raw", 250, 0, 250); + TProfile* tp_DX_vs_X_smooth = new TProfile("tp_DX_vs_X_smooth", "tp_DX_vs_X_smooth", 250, 0, 250); + TProfile* tp_DX_vs_X_smooth_extr = new TProfile("tp_DX_vs_X_smooth_extr", "tp_DX_vs_X_smooth_extr", 250, 0, 250); + TProfile* tp_DY_vs_X_smooth_extr = new TProfile("tp_DY_vs_X_smooth_extr", "tp_DY_vs_X_smooth_extr", 250, 0, 250); + TProfile* tp_DZ_vs_X_smooth_extr_A = new TProfile("tp_DZ_vs_X_smooth_extr_A", "tp_DZ_vs_X_smooth_extr_A", 250, 0, 250); + TProfile* tp_DZ_vs_X_smooth_extr_C = new TProfile("tp_DZ_vs_X_smooth_extr_C", "tp_DZ_vs_X_smooth_extr_C", 250, 0, 250); + TProfile* tp_DZ_vs_X_smooth_A = new TProfile("tp_DZ_vs_X_smooth_A", "tp_DZ_vs_X_smooth_A", 250, 0, 250); + TProfile* tp_DZ_vs_X_smooth_C = new TProfile("tp_DZ_vs_X_smooth_C", "tp_DZ_vs_X_smooth_C", 250, 0, 250); + TProfile* tp_DZ_vs_X_raw_A = new TProfile("tp_DZ_vs_X_raw_A", "tp_DZ_vs_X_raw_A", 250, 0, 250); + TProfile* tp_DZ_vs_X_raw_C = new TProfile("tp_DZ_vs_X_raw_C", "tp_DZ_vs_X_raw_C", 250, 0, 250); + TProfile* tp_Stat_vs_row = new TProfile("tp_Stat_vs_row", "tp_Stat_vs_row;row;", 152, 0, 152); + int n_bins_z_phi_sector = 36 * nY2XBins * nZ2XBins; + TH1D* h_x_start_fit_vs_z_phi_sector = new TH1D("h_x_start_fit_vs_z_phi_sector", "h_x_start_fit_vs_z_phi_sector", n_bins_z_phi_sector, 0, n_bins_z_phi_sector); + //---------------------------------------------------------------- + + //-------------------------------------------------------------------------- + // Prepare output data + std::vector>>>> vec_DXYZ_vox; + std::vector>>>> vec_DXYZ_vox_GF; + vec_DXYZ_vox.resize(6); + vec_DXYZ_vox_GF.resize(6); + for (Int_t i_xyz = 0; i_xyz < 6; i_xyz++) { + vec_DXYZ_vox[i_xyz].resize(36); + vec_DXYZ_vox_GF[i_xyz].resize(36); + for (Int_t i_sector = 0; i_sector < 36; i_sector++) { + vec_DXYZ_vox[i_xyz][i_sector].resize(152); + vec_DXYZ_vox_GF[i_xyz][i_sector].resize(152); + for (Int_t voxX = 0; voxX < 152; voxX++) { + vec_DXYZ_vox[i_xyz][i_sector][voxX].resize((nY2XBins)); + vec_DXYZ_vox_GF[i_xyz][i_sector][voxX].resize((nY2XBins)); + for (Int_t voxY = 0; voxY < (nY2XBins); voxY++) { + vec_DXYZ_vox[i_xyz][i_sector][voxX][voxY].resize((nZ2XBins)); + vec_DXYZ_vox_GF[i_xyz][i_sector][voxX][voxY].resize((nZ2XBins)); + for (Int_t voxZ = 0; voxZ < (nZ2XBins); voxZ++) { + vec_DXYZ_vox[i_xyz][i_sector][voxX][voxY][voxZ] = 0.0; + vec_DXYZ_vox_GF[i_xyz][i_sector][voxX][voxY][voxZ] = 0.0; + } + } + } + } + } + std::vector>> vec_max_X_fit; + vec_max_X_fit.resize(36); + for (Int_t i_sector = 0; i_sector < 36; i_sector++) { + vec_max_X_fit[i_sector].resize(nY2XBins); + for (Int_t voxY = 0; voxY < (nY2XBins); voxY++) { + vec_max_X_fit[i_sector][voxY].resize(nZ2XBins); + for (Int_t voxZ = 0; voxZ < (nZ2XBins); voxZ++) { + vec_max_X_fit[i_sector][voxY][voxZ] = 0.0; + } + } + } + + for (Long64_t jentry = 0; jentry < entries_input_map; jentry++) { + voxResTree->GetEntry(jentry); + + const auto bvox_X = voxRes_map->bvox[o2::tpc::TrackResiduals::VoxX]; // bin number in x (= pad row) + const auto bvox_F = voxRes_map->bvox[o2::tpc::TrackResiduals::VoxF]; // bin number in y/x 0..14 + const auto bvox_Z = voxRes_map->bvox[o2::tpc::TrackResiduals::VoxZ]; // bin number in z/x 0..4 + const int sector = (int)voxRes_map->bsec; + const float xAV = voxRes_map->stat[o2::tpc::TrackResiduals::VoxX]; + const float z2xAV = voxRes_map->stat[o2::tpc::TrackResiduals::VoxZ]; + const float zAV = z2xAV * xAV; + + vec_DXYZ_vox[0][sector][bvox_X][bvox_F][bvox_Z] = voxRes_map->D[0]; // dX + vec_DXYZ_vox[1][sector][bvox_X][bvox_F][bvox_Z] = voxRes_map->D[1]; // dY + vec_DXYZ_vox[2][sector][bvox_X][bvox_F][bvox_Z] = voxRes_map->D[2]; // dZ + vec_DXYZ_vox[3][sector][bvox_X][bvox_F][bvox_Z] = voxRes_map->stat[3]; // #entries + vec_DXYZ_vox[4][sector][bvox_X][bvox_F][bvox_Z] = xAV; // xAV + vec_DXYZ_vox[5][sector][bvox_X][bvox_F][bvox_Z] = zAV; // zAV + + vec_DXYZ_vox_GF[0][sector][bvox_X][bvox_F][bvox_Z] = voxRes_map->DS[0]; // dXS + vec_DXYZ_vox_GF[1][sector][bvox_X][bvox_F][bvox_Z] = voxRes_map->DS[1]; // dYS + vec_DXYZ_vox_GF[2][sector][bvox_X][bvox_F][bvox_Z] = voxRes_map->DS[2]; // dZS + vec_DXYZ_vox_GF[3][sector][bvox_X][bvox_F][bvox_Z] = voxRes_map->stat[3]; // #entries + vec_DXYZ_vox_GF[4][sector][bvox_X][bvox_F][bvox_Z] = xAV; // xAV + vec_DXYZ_vox_GF[5][sector][bvox_X][bvox_F][bvox_Z] = zAV; // zAV + + tp_Stat_vs_row->Fill(bvox_X, voxRes_map->stat[3]); + float voxX_pos = RowX[bvox_X]; + if (fabs(bvox_F - (int)(nY2XBins / 2)) <= 1) { + tp_Stat_vs_X->Fill(voxX_pos, voxRes_map->stat[3]); + } + if (bvox_Z == 0) { + // if(voxX_pos < (85.0+32.0)) + { + tp_DX_vs_X_raw->Fill(voxX_pos, voxRes_map->D[0]); + tp_DX_vs_Row_raw->Fill(bvox_X, voxRes_map->D[0]); + if (sector < 18) { + tp_DZ_vs_X_raw_A->Fill(voxX_pos, voxRes_map->D[2]); + } else { + tp_DZ_vs_X_raw_C->Fill(voxX_pos, voxRes_map->D[2]); + } + } + } + } + + //-------------------------------------------------------------------------------- + tp_Stat_vs_row->GetXaxis()->SetRangeUser(20, 62); + const float meanEntries = tp_Stat_vs_row->GetMean(2); + tp_Stat_vs_row->GetXaxis()->SetRangeUser(2, 1); + const int startRowGoodEntries = tp_Stat_vs_row->FindFirstBinAbove(meanEntries * 0.7) - 1; + + // don't trust bins with too low statistics + tp_DX_vs_X_raw->GetXaxis()->SetRangeUser(RowX[startRowGoodEntries], RowX[63]); + // const float max_DX = tp_DX_vs_X_raw ->GetBinContent(tp_DX_vs_X_raw->GetMaximumBin()); + // const float max_X_DX = tp_DX_vs_X_raw ->GetBinCenter(tp_DX_vs_X_raw->GetMaximumBin()); + tp_DX_vs_Row_raw->GetXaxis()->SetRangeUser(startRowGoodEntries, 63); + // const int max_Row_DX = tp_DX_vs_Row_raw ->GetBinCenter(tp_DX_vs_Row_raw->GetMaximumBin()); + + float max_X_stat = 0.0; + for (int ibin = (tp_Stat_vs_X->GetNbinsX() - 3); ibin >= 0; ibin--) { + float X_val = tp_Stat_vs_X->GetBinCenter(ibin); + float stat = tp_Stat_vs_X->GetBinContent(ibin); + float DX = tp_DX_vs_X_raw->GetBinContent(ibin); + if (X_val < (85.0 + 32.0)) { + Double_t stat_previous[3] = {tp_Stat_vs_X->GetBinContent(ibin + 1), tp_Stat_vs_X->GetBinContent(ibin + 2), tp_Stat_vs_X->GetBinContent(ibin + 3)}; + Double_t Xpos_previous[3] = {tp_Stat_vs_X->GetBinCenter(ibin + 1), tp_Stat_vs_X->GetBinCenter(ibin + 2), tp_Stat_vs_X->GetBinCenter(ibin + 3)}; + if ((stat - stat_previous[0]) < 0.0 && (stat - stat_previous[1]) < 0.0 && (stat - stat_previous[2]) < 0.0 && stat > 0.0) { + Double_t ratio_stat[3] = {stat / stat_previous[0], stat / stat_previous[1], stat / stat_previous[2]}; + if (ratio_stat[0] < 0.8 && ratio_stat[1] < 0.5 && ratio_stat[2] < 0.5) { + max_X_stat = Xpos_previous[2]; + break; + } + } + if (fabs(stat < 0.1)) { + max_X_stat = Xpos_previous[2]; + break; + } + } + } + + const float max_X_DX = max_X_stat; + int max_Row_DX = 0; + + // find corresponding row + for (int i = 0; i < 50; ++i) { + if (RowX[i] > max_X_DX) { + break; + } + max_Row_DX = i; + } + const float max_DX = tp_DX_vs_X_raw->GetBinContent(tp_DX_vs_X_raw->FindBin(max_X_DX)); + + LOGP(info, "max DX value: {:.3f}, X-position of max DX value: {:.3f} (row: {}), first row checked: {}, mean entries: {:.2f}, max_X_stat: {:.3f}", max_DX, max_X_DX, max_Row_DX, startRowGoodEntries, meanEntries, max_X_stat); + //-------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------- + LOGP(info, "Calculating extrapolation fit start values for every phi slice"); + for (Int_t i_sector = 0; i_sector < 36; i_sector++) { + for (Int_t voxY = 0; voxY < (nY2XBins); voxY++) { + for (Int_t voxZ = 0; voxZ < (nZ2XBins); voxZ++) { + // fill the TGraph used for fitting + int ipoint = 0; + for (Int_t voxX = 0; voxX <= 151; voxX++) { + float voxX_pos = RowX[voxX]; + float statistics = vec_DXYZ_vox_GF[3][i_sector][voxX][voxY][voxZ]; + float DX = vec_DXYZ_vox_GF[0][i_sector][voxX][voxY][voxZ]; + tg_Stat_vs_X_slice->SetPoint(ipoint, voxX_pos, statistics); + ipoint++; + + if (i_sector == 11 && voxY == 10 && voxZ == 27) { + tp_Stat_vs_X_single->Fill(voxX_pos, statistics); + tp_DX_vs_X_single->Fill(voxX_pos, DX); + } + } + + float max_X_stat = 0.0; + for (int ibin = (tg_Stat_vs_X_slice->GetN() - 3); ibin >= 0; ibin--) { + double X_val = 0.0; + double stat = 0.0; + tg_Stat_vs_X_slice->GetPoint(ibin, X_val, stat); + if (stat < min_statistics) + continue; + if (X_val < (85.0 + 32.0)) { + Double_t stat_previous[3] = {0.0, 0.0, 0.0}; + Double_t Xpos_previous[3] = {0.0, 0.0, 0.0}; + + tg_Stat_vs_X_slice->GetPoint(ibin + 1, Xpos_previous[0], stat_previous[0]); + tg_Stat_vs_X_slice->GetPoint(ibin + 2, Xpos_previous[1], stat_previous[1]); + tg_Stat_vs_X_slice->GetPoint(ibin + 3, Xpos_previous[2], stat_previous[2]); + + if ((stat - stat_previous[0]) < 0.0 && (stat - stat_previous[1]) < 0.0 && (stat - stat_previous[2]) < 0.0 && stat > 0.0) { + Double_t ratio_stat[3] = {stat / stat_previous[0], stat / stat_previous[1], stat / stat_previous[2]}; + // if(i_sector == 9 && voxY == 19 && voxZ == 27) + //{ + // } + if (ratio_stat[0] < 0.8 && ratio_stat[1] < 0.5 && ratio_stat[2] < 0.5) { + // first check if there aren't any bins at lower radii with statistics + int flag_low_bin = 0; + for (int ibinB = ibin - 1; ibinB >= 0; ibinB--) { + double X_valB = 0.0; + double statB = 0.0; + tg_Stat_vs_X_slice->GetPoint(ibinB, X_valB, statB); + if (statB > 0.0 && statB / stat_previous[0] > 0.8) { + ibin = ibinB; + flag_low_bin = 1; + break; + } + } + if (!flag_low_bin) { + max_X_stat = Xpos_previous[2]; + break; + } + } + } + if (fabs(stat < 0.1)) { + // first check if there aren't any bins at lower radii with statistics + int flag_low_bin = 0; + for (int ibinB = ibin - 1; ibinB >= 0; ibinB--) { + double X_valB = 0.0; + double statB = 0.0; + tg_Stat_vs_X_slice->GetPoint(ibinB, X_valB, statB); + if (statB > 0.0) { + ibin = ibinB; + flag_low_bin = 1; + break; + } + } + if (!flag_low_bin) { + max_X_stat = Xpos_previous[2]; + break; + } + } + } + } + + if (max_X_stat < 85.0) { + max_X_stat = max_X_DX; // set to average value + } + int i_bin_z_phi_sector = voxZ * 36 * nY2XBins + i_sector * nY2XBins + voxY; + h_x_start_fit_vs_z_phi_sector->SetBinContent(i_bin_z_phi_sector, max_X_stat); + tg_Stat_vs_X_slice->Set(0); + + vec_max_X_fit[i_sector][voxY][voxZ] = max_X_stat; + } // end of Z loop + } // end of Y loop + } // end of sector loop + LOGP(info, "Done calculating extrapolation fit start values for every phi slice"); + //-------------------------------------------------------------------------------- + + // ========================================================================= + // treat acceptance edge in z-direction + // replace values very close to or beyond the pad plane by a value a bit further away + const float maxZ = 242.f; + for (Int_t i_sector = 0; i_sector < 36; i_sector++) { + for (Int_t voxX = 0; voxX < nXBins; voxX++) { + for (Int_t voxY = 0; voxY < nY2XBins; voxY++) { + float lastValue[4] = {0.f, 0.f, 0.f, 0.f}; + float lastValueS[4] = {0.f, 0.f, 0.f, 0.f}; + float lastValueA11[4] = {0.f, 0.f, 0.f, 0.f}; + float lastValueSA11[4] = {0.f, 0.f, 0.f, 0.f}; + for (Int_t voxZ = 0; voxZ < nZ2XBins; voxZ++) { + const float absZ = std::abs(vec_DXYZ_vox[5][i_sector][voxX][voxY][voxZ]); + // if (i_sector==0&&voxX>149&&voxY==5) { + //} + if (absZ < maxZ) { + for (int i = 0; i < 4; ++i) { + lastValue[i] = vec_DXYZ_vox[i][i_sector][voxX][voxY][voxZ]; + lastValueS[i] = vec_DXYZ_vox_GF[i][i_sector][voxX][voxY][voxZ]; + } + } else { + for (int i = 0; i < 4; ++i) { + vec_DXYZ_vox[i][i_sector][voxX][voxY][voxZ] = lastValue[i]; + vec_DXYZ_vox_GF[i][i_sector][voxX][voxY][voxZ] = lastValueS[i]; + } + } + + if (i_sector == 11 && A11maxZ2X > -1) { + if (voxZ <= A11maxZ2X) { + for (int i = 0; i < 4; ++i) { + lastValueA11[i] = vec_DXYZ_vox[i][i_sector][voxX][voxY][voxZ]; + lastValueSA11[i] = + vec_DXYZ_vox_GF[i][i_sector][voxX][voxY][voxZ]; + } + } else { + for (int i = 0; i < 4; ++i) { + vec_DXYZ_vox[i][i_sector][voxX][voxY][voxZ] = lastValueA11[i]; + vec_DXYZ_vox_GF[i][i_sector][voxX][voxY][voxZ] = + lastValueSA11[i]; + } + } + } + } + } + } + } + + //---------------------------------------------------------------- + // Gaussian filtering + + if (do_smoothing) { + LOGP(info, "Gaussian filtering started"); + std::vector>> vec_GKernel; + vec_GKernel.resize(N_bins_X_GF * 2 + 1); + for (Int_t i_X = 0; i_X < (Int_t)vec_GKernel.size(); i_X++) { + vec_GKernel[i_X].resize(N_bins_Y_GF * 2 + 1); + for (Int_t i_Y = 0; i_Y < (Int_t)vec_GKernel[i_X].size(); i_Y++) { + vec_GKernel[i_X][i_Y].resize(N_bins_Z_GF * 2 + 1); + } + } + vec_FilterCreation(vec_GKernel, N_bins_X_GF, N_bins_Y_GF, N_bins_Z_GF, sigma_GF); + + for (Int_t i_sector = 0; i_sector < 36; i_sector++) { + std::vector>>> arr_values; + std::vector>>> arr_values_used; + arr_values.resize(3); + arr_values_used.resize(3); + + for (Int_t i_xyz = 0; i_xyz < 3; i_xyz++) { + arr_values[i_xyz].resize(N_bins_X_GF * 2 + 1); + arr_values_used[i_xyz].resize(N_bins_X_GF * 2 + 1); + for (Int_t i_X = 0; i_X < (Int_t)arr_values[i_xyz].size(); i_X++) { + arr_values[i_xyz][i_X].resize(N_bins_Y_GF * 2 + 1); + arr_values_used[i_xyz][i_X].resize(N_bins_Y_GF * 2 + 1); + for (Int_t i_Y = 0; i_Y < (Int_t)arr_values[i_xyz][i_X].size(); i_Y++) { + arr_values[i_xyz][i_X][i_Y].resize(N_bins_Z_GF * 2 + 1); + arr_values_used[i_xyz][i_X][i_Y].resize(N_bins_Z_GF * 2 + 1); + for (Int_t i_Z = 0; i_Z < (Int_t)arr_values[i_xyz][i_X][i_Y].size(); i_Z++) { + arr_values[i_xyz][i_X][i_Y][i_Z] = 0.0; + arr_values_used[i_xyz][i_X][i_Y][i_Z] = 0.0; + } + } + } + } + + // CRU 0 : 000 - 016 (IROC) + // CRU 1 : 017 - 031 (IROC) + // CRU 2 : 032 - 047 (IROC) + // CRU 3 : 048 - 062 (IROC) + + // CRU 4 : 063 - 080 (OROC 1) + // CRU 5 : 081 - 096 (OROC 1) + + // CRU 6 : 097 - 112 (OROC 2) + // CRU 7 : 113 - 126 (OROC 2) + + // CRU 8 : 127 - 139 (OROC 3) + // CRU 9 : 140 - 151 (OROC 3) + + std::vector> vec_ROC_row; + vec_ROC_row.resize(4); + for (int iRoc = 0; iRoc < 4; iRoc++) { + vec_ROC_row[iRoc].resize(2); + } + vec_ROC_row[0][0] = 0; + vec_ROC_row[0][1] = 62; + vec_ROC_row[1][0] = 63; + vec_ROC_row[1][1] = 96; + vec_ROC_row[2][0] = 97; + vec_ROC_row[2][1] = 126; + vec_ROC_row[3][0] = 127; + vec_ROC_row[3][1] = 151; + + for (int iRoc = 0; iRoc < 4; iRoc++) { + int minRow = vec_ROC_row[iRoc][0]; + if (do_extrapolation && (iRoc == 0)) { + minRow = max_Row_DX + 2; // don't smooth over low radii large distortions + } + for (Int_t voxX = minRow; voxX <= vec_ROC_row[iRoc][1]; voxX++) { + for (Int_t voxY = 0; voxY < (nY2XBins); voxY++) { + for (Int_t voxZ = 0; voxZ < (nZ2XBins); voxZ++) { + Float_t sum_weight[3] = {0.0}; + Float_t sum_values[3] = {0.0}; + for (Int_t index_voxXB = -N_bins_X_GF; index_voxXB <= N_bins_X_GF; index_voxXB++) { + Int_t voxXB = voxX + index_voxXB; + if (voxXB < vec_ROC_row[iRoc][0]) + continue; + if (voxXB > vec_ROC_row[iRoc][1]) + continue; + for (Int_t index_voxYB = -N_bins_Y_GF; index_voxYB <= N_bins_Y_GF; index_voxYB++) { + Int_t voxYB = voxY + index_voxYB; + if (voxYB < 0) + continue; + if (voxYB >= nY2XBins) + continue; + for (Int_t index_voxZB = -N_bins_Z_GF; index_voxZB <= N_bins_Z_GF; index_voxZB++) { + Int_t voxZB = voxZ + index_voxZB; + if (voxZB < 0) + continue; + if (voxZB >= (nZ2XBins)) + continue; + float statistics = vec_DXYZ_vox[3][i_sector][voxXB][voxYB][voxZB]; + if ((int)statistics == 0) + continue; + if (TMath::IsNaN(vec_DXYZ_vox[0][i_sector][voxXB][voxYB][voxZB])) + continue; // NaN check + if (TMath::IsNaN(vec_DXYZ_vox[1][i_sector][voxXB][voxYB][voxZB])) + continue; // NaN check + if (TMath::IsNaN(vec_DXYZ_vox[2][i_sector][voxXB][voxYB][voxZB])) + continue; // NaN check + if (fabs(vec_DXYZ_vox[0][i_sector][voxXB][voxYB][voxZB]) > maxDeltaCut) + continue; + if (fabs(vec_DXYZ_vox[1][i_sector][voxXB][voxYB][voxZB]) > maxDeltaCut) + continue; + if (fabs(vec_DXYZ_vox[2][i_sector][voxXB][voxYB][voxZB]) > maxDeltaCut) + continue; + for (Int_t i_xyz = 0; i_xyz < 3; i_xyz++) { + arr_values_used[i_xyz][index_voxXB + N_bins_X_GF][index_voxYB + N_bins_Y_GF][index_voxZB + N_bins_Z_GF] = 1.0; + arr_values[i_xyz][index_voxXB + N_bins_X_GF][index_voxYB + N_bins_Y_GF][index_voxZB + N_bins_Z_GF] = vec_GKernel[index_voxXB + N_bins_X_GF][index_voxYB + N_bins_Y_GF][index_voxZB + N_bins_Z_GF] * vec_DXYZ_vox[i_xyz][i_sector][voxXB][voxYB][voxZB]; + sum_weight[i_xyz] += vec_GKernel[index_voxXB + N_bins_X_GF][index_voxYB + N_bins_Y_GF][index_voxZB + N_bins_Z_GF]; + sum_values[i_xyz] += arr_values[i_xyz][index_voxXB + N_bins_X_GF][index_voxYB + N_bins_Y_GF][index_voxZB + N_bins_Z_GF]; + if (TMath::IsNaN(vec_DXYZ_vox[i_xyz][i_sector][voxXB][voxYB][voxZB])) { + LOGP(error, "NaN vec_DXYZ_vox detected in xyz {}, sec {}, voxX {}, voxY {}, voxZ {}", i_xyz, i_sector, voxXB, voxYB, voxZB); + } + if (TMath::IsNaN(sum_values[i_xyz])) { + LOGP(error, "NaN sum_values detected in xyz {}, sec {}, voxX {}, voxY {}, voxZ {}", i_xyz, i_sector, voxXB, voxYB, voxZB); + } + } + } + } + } + + for (Int_t i_xyz = 0; i_xyz < 3; i_xyz++) { + if (sum_weight[i_xyz] > 0.0) { + sum_values[i_xyz] /= sum_weight[i_xyz]; + vec_DXYZ_vox_GF[i_xyz][i_sector][voxX][voxY][voxZ] = sum_values[i_xyz]; + if (TMath::IsNaN(vec_DXYZ_vox_GF[i_xyz][i_sector][voxX][voxY][voxZ])) { + LOGP(error, "NaN detected during smoothing process in xyz {}, sec {}, voxX {}, voxY {}, voxZ {}", i_xyz, i_sector, voxX, voxY, voxZ); + } + + float voxX_pos = RowX[voxX]; + if (voxZ == 0) { + if (i_xyz == 0) + tp_DX_vs_X_smooth->Fill(voxX_pos, sum_values[i_xyz]); + if (i_sector < 18) { + if (i_xyz == 2) + tp_DZ_vs_X_smooth_A->Fill(voxX_pos, sum_values[i_xyz]); + } else { + if (i_xyz == 2) + tp_DZ_vs_X_smooth_C->Fill(voxX_pos, sum_values[i_xyz]); + } + } + } + } + } + } + } + } + } // end loop over sectors + } // do_smoothing + //---------------------------------------------------------------- + + //---------------------------------------------------------------- + // Do extrapolation to low radii + if (do_extrapolation > 0) { + LOGP(info, "Starting extrapolation to small radii"); + // const float start_fit = max_X_DX + 0.0; + // const float stop_fit = max_X_DX + 10.0; + TGraph* tg_data_for_fit = new TGraph(); + TGraph* tg_data_for_fit_raw = new TGraph(); + for (Int_t i_sector = 0; i_sector < 36; i_sector++) { + for (Int_t voxY = 0; voxY < (nY2XBins); voxY++) { + for (Int_t voxZ = 0; voxZ < (nZ2XBins); voxZ++) { + const float start_fit = vec_max_X_fit[i_sector][voxY][voxZ] + 0.0; + const float stop_fit = vec_max_X_fit[i_sector][voxY][voxZ] + 10.0; + // find maximum dX + // float maxXdX = 0; + // float maxdX = 0; + // for(Int_t voxX = startRowGoodEntries; voxX < 63; voxX++) // enough to search in IROC + //{ + // const float dX = vec_DXYZ_vox[0][i_sector][voxX][voxY][voxZ]; + // if (dX > maxdX) { + // maxdX = dX; + // maxXdX = RowX[voxX]; + //} + //} + // const float start_fit = maxXdX + 1.0; + // const float stop_fit = maxXdX + 10.0; + + for (Int_t i_xyz = 0; i_xyz < 3; i_xyz++) { + // fill the TGraph used for fitting + int ipoint = 0; + int meanStat = 1; // statistics to use in extrapolation region + for (Int_t voxX = 0; voxX <= 151; voxX++) { + // float voxX_pos = vec_DXYZ_vox_GF[4][i_sector][voxX][voxY][voxZ]; + float voxX_pos = RowX[voxX]; + if (voxX_pos < start_fit) + continue; + if (voxX_pos > stop_fit) + break; + float statistics = vec_DXYZ_vox_GF[3][i_sector][voxX][voxY][voxZ]; + if (statistics < min_statistics) + continue; + float DXYZval = vec_DXYZ_vox_GF[i_xyz][i_sector][voxX][voxY][voxZ]; + tg_data_for_fit->SetPoint(ipoint, voxX_pos, DXYZval); + float DXYZval_raw = vec_DXYZ_vox[i_xyz][i_sector][voxX][voxY][voxZ]; + tg_data_for_fit_raw->SetPoint(ipoint, voxX_pos, DXYZval_raw); + ipoint++; + } + if (ipoint < 5) + continue; + + // fit the TGraph + for (Int_t i = 0; i < 6; i++) { + func_PolyFitFunc->SetParameter(i, 0.0); + func_PolyFitFunc->SetParError(i, 0.0); + func_PolyFitFunc_raw->SetParameter(i, 0.0); + func_PolyFitFunc_raw->SetParError(i, 0.0); + if (i > 2) { + func_PolyFitFunc->FixParameter(i, 0.0); + func_PolyFitFunc_raw->FixParameter(i, 0.0); + } + } + func_PolyFitFunc->SetParameter(0, 0.2); + func_PolyFitFunc->SetParameter(1, 0.3); + func_PolyFitFunc->SetParameter(2, 0.4); + func_PolyFitFunc->SetRange(start_fit, stop_fit); + tg_data_for_fit->Fit("func_PolyFitFunc", "QWMN", "", start_fit, stop_fit); + + func_PolyFitFunc_raw->SetParameter(0, 0.2); + func_PolyFitFunc_raw->SetParameter(1, 0.3); + func_PolyFitFunc_raw->SetParameter(2, 0.4); + func_PolyFitFunc_raw->SetRange(start_fit, stop_fit); + tg_data_for_fit_raw->Fit("func_PolyFitFunc_raw", "QWMN", "", start_fit, stop_fit); + + // if (ipoint>0) { + // meanStat /= ipoint; + // } + + // do the low radii extrapolation + for (Int_t voxX = 0; voxX <= 151; voxX++) { + // float voxX_pos = vec_DXYZ_vox_GF[4][i_sector][voxX][voxY][voxZ]; + float voxX_pos = RowX[voxX]; + if (voxX_pos > start_fit) { + break; + } + double extrapolation_value = func_PolyFitFunc->Eval(voxX_pos); + if (fabs(extrapolation_value) > max_extrapolation_value) + extrapolation_value = TMath::Sign(1, extrapolation_value) * max_extrapolation_value; + vec_DXYZ_vox_GF[i_xyz][i_sector][voxX][voxY][voxZ] = extrapolation_value; + vec_DXYZ_vox_GF[3][i_sector][voxX][voxY][voxZ] = meanStat; // set statistics -> important for spline creation + + double extrapolation_value_raw = func_PolyFitFunc_raw->Eval(voxX_pos); + if (fabs(extrapolation_value_raw) > max_extrapolation_value) + extrapolation_value_raw = TMath::Sign(1, extrapolation_value_raw) * max_extrapolation_value; + vec_DXYZ_vox[i_xyz][i_sector][voxX][voxY][voxZ] = extrapolation_value_raw; + vec_DXYZ_vox[3][i_sector][voxX][voxY][voxZ] = meanStat; // set statistics -> important for spline creation + } + + tg_data_for_fit->Set(0); + tg_data_for_fit_raw->Set(0); + } // end of xyz + + for (Int_t voxX = 0; voxX <= 151; voxX++) { + if (voxZ == 0) // for QA + { + float voxX_pos = RowX[voxX]; + tp_DX_vs_X_smooth_extr->Fill(voxX_pos, vec_DXYZ_vox_GF[0][i_sector][voxX][voxY][voxZ]); + if (i_sector < 18) { + tp_DZ_vs_X_smooth_extr_A->Fill(voxX_pos, vec_DXYZ_vox_GF[2][i_sector][voxX][voxY][voxZ]); + } else { + tp_DZ_vs_X_smooth_extr_C->Fill(voxX_pos, vec_DXYZ_vox_GF[2][i_sector][voxX][voxY][voxZ]); + } + } + } + } + } + } + } + //---------------------------------------------------------------- + + //---------------------------------------------------------------- + // IROC A11 maskign + if (maskIA11) { + int i_sector = 11; + for (Int_t voxY = 0; voxY < (nY2XBins); voxY++) { + for (Int_t voxZ = 0; voxZ < (nZ2XBins); voxZ++) { + for (Int_t voxX = 0; voxX <= 62; voxX++) { + for (Int_t i_xyz = 0; i_xyz < 4; i_xyz++) { + vec_DXYZ_vox_GF[i_xyz][i_sector][voxX][voxY][voxZ] = 0; + vec_DXYZ_vox[i_xyz][i_sector][voxX][voxY][voxZ] = 0; + } + } + } + } + } + + //---------------------------------------------------------------- + mTreeOut = std::make_unique("voxResTree", "Voxel results and statistics"); + mTreeOut->Branch("voxRes", &mVoxelResultsOutPtr); + // copy user info + auto userInfoOut = mTreeOut->GetUserInfo(); + for (auto o : *userInfo) { + userInfoOut->Add(o->Clone()); + } + // Overwrite the placeholder meanIDC/medianIDC just cloned above with the real offline-joined + // values computed in the "Offline IDC join" block earlier in this function. + if (auto* stale = userInfoOut->FindObject("meanIDC")) { + userInfoOut->Remove(stale); + delete stale; + } + if (auto* stale = userInfoOut->FindObject("medianIDC")) { + userInfoOut->Remove(stale); + delete stale; + } + userInfoOut->Add(new TNamed("meanIDC", std::to_string(meanIDCReal).data())); + userInfoOut->Add(new TNamed("medianIDC", std::to_string(medianIDCReal).data())); + userInfoOut->Add(new TNamed("startRowGoodEntries", std::to_string(startRowGoodEntries).data())); + userInfoOut->Add(new TNamed("maxDX", std::to_string(max_DX).data())); + userInfoOut->Add(new TNamed("maxDX_lx", std::to_string(max_X_DX).data())); + userInfoOut->Add(new TNamed("maxDX_row", std::to_string(max_Row_DX).data())); + + // copy aliases + if (voxResTree->GetListOfAliases()) { + for (auto o : *voxResTree->GetListOfAliases()) { + mTreeOut->SetAlias(o->GetName(), o->GetTitle()); + } + } + + for (Long64_t jentry = 0; jentry < entries_input_map; jentry++) { + voxResTree->GetEntry(jentry); + + auto bvox_X = voxRes_map->bvox[o2::tpc::TrackResiduals::VoxX]; // bin number in x (= pad row) + auto bvox_F = voxRes_map->bvox[o2::tpc::TrackResiduals::VoxF]; // bin number in y/x 0..14 + auto bvox_Z = voxRes_map->bvox[o2::tpc::TrackResiduals::VoxZ]; // bin number in z/x 0..4 + int sector = (int)voxRes_map->bsec; + // Int_t index_map = bvox_X+152*bvox_F+152*(nY2XBins)*bvox_Z+152*(nY2XBins)*(nZ2XBins)*sector; + + mVoxelResultsOut = *voxRes_map; // copy the entry from the input map + + for (int ixyz = 0; ixyz < 3; ixyz++) { + if (do_extrapolation == 1 || do_extrapolation == 2) + mVoxelResultsOut.DS[ixyz] = (float)vec_DXYZ_vox_GF[ixyz][sector][bvox_X][bvox_F][bvox_Z]; // overwrite the smoothed values + if (do_extrapolation == 2 || do_extrapolation == 3) + mVoxelResultsOut.D[ixyz] = (float)vec_DXYZ_vox[ixyz][sector][bvox_X][bvox_F][bvox_Z]; // overwrite the raw values + } + if (do_extrapolation == 1 || do_extrapolation == 2) + mVoxelResultsOut.stat[3] = (float)vec_DXYZ_vox_GF[3][sector][bvox_X][bvox_F][bvox_Z]; + if (do_extrapolation == 2 || do_extrapolation == 3) + mVoxelResultsOut.stat[3] = (float)vec_DXYZ_vox[3][sector][bvox_X][bvox_F][bvox_Z]; + for (int ixyz = 0; ixyz < 3; ixyz++) { + if (TMath::IsNaN(mVoxelResultsOut.DS[ixyz])) // NaN + { + LOGP(error, "NaN detected in smoothed value xyz {}, sec {}, voxX {}, voxY {}, voxZ {}", ixyz, sector, bvox_X, bvox_F, bvox_Z); + mVoxelResultsOut.DS[ixyz] = 0.0; + mVoxelResultsOut.stat[3] = 0; + mVoxelResultsOut.flags |= TrackResiduals::Masked; + } else { + mVoxelResultsOut.flags |= TrackResiduals::SmoothDone; + } + if (TMath::IsNaN(mVoxelResultsOut.D[ixyz])) // NaN + { + LOGP(error, "NaN detected in raw value xyz {}, sec {}, voxX {}, voxY {}, voxZ {}", ixyz, sector, bvox_X, bvox_F, bvox_Z); + mVoxelResultsOut.D[ixyz] = 0.0; + } + } + mTreeOut->Fill(); + } + //---------------------------------------------------------------- + + //---------------------------------------------------------------- + outputfile->cd(); + mTreeOut->Write(); + mTreeOut.release(); + tp_DX_vs_X_raw->Write(); + tp_DX_vs_X_smooth->Write(); + tp_DX_vs_X_smooth_extr->Write(); + tp_DZ_vs_X_raw_A->Write(); + tp_DZ_vs_X_raw_C->Write(); + tp_DZ_vs_X_smooth_A->Write(); + tp_DZ_vs_X_smooth_C->Write(); + tp_DZ_vs_X_smooth_extr_A->Write(); + tp_DZ_vs_X_smooth_extr_C->Write(); + tp_Stat_vs_X->Write(); + tp_Stat_vs_row->Write(); + h_x_start_fit_vs_z_phi_sector->Write(); + tp_Stat_vs_X_single->Write(); + tp_DX_vs_X_single->Write(); + outputfile->Close(); + //---------------------------------------------------------------- +} diff --git a/Detectors/TPC/calibration/SpacePoints/macro/staticMapCreatorCPM.C b/Detectors/TPC/calibration/SpacePoints/macro/staticMapCreatorCPM.C new file mode 100644 index 0000000000000..dea2b2f98c2ef --- /dev/null +++ b/Detectors/TPC/calibration/SpacePoints/macro/staticMapCreatorCPM.C @@ -0,0 +1,2564 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#if !defined(__CLING__) || defined(__ROOTCLING__) +#include "CCDB/CcdbApi.h" +#include "CCDB/BasicCCDBManager.h" + +#include "TSystem.h" +#include "Algorithm/RangeTokenizer.h" +#include "Framework/Logger.h" +#include "CommonConstants/LHCConstants.h" +#include "SpacePoints/SpacePointsCalibConfParam.h" +#include "SpacePoints/TrackResiduals.h" +#include "SpacePoints/TrackInterpolation.h" +#include "DataFormatsParameters/GRPMagField.h" +#include "DataFormatsParameters/GRPLHCIFData.h" +#include "DataFormatsTPC/Defs.h" +#include "ReconstructionDataFormats/GlobalTrackID.h" +#include "DetectorsBase/MatLayerCylSet.h" +#include "DetectorsBase/Propagator.h" +#include "TPCBase/Mapper.h" +#include "ReconstructionDataFormats/TrackUtils.h" + +#include +#include +#include +#include +#include "TTreePerfStats.h" +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +// For multiple threads +#include +#include +#include +#include +#include +#include "TROOT.h" + +// Compile-time detection of TrackData::filterFlag / UnbinnedResid::rejected, for back-compat with O2 +// builds that predate them. +template +struct HasFilterFlagMember : std::false_type { +}; +template +struct HasFilterFlagMember().filterFlag)>> : std::true_type { +}; + +template +struct HasRejectedMember : std::false_type { +}; +template +struct HasRejectedMember().rejected)>> : std::true_type { +}; + +template +bool hasPositiveFilterFlag(const T& t) +{ + if constexpr (HasFilterFlagMember::value) { + return t.filterFlag > 0; + } else { + return false; + } +} + +template +bool isRejectedResidual(const T& t) +{ + if constexpr (HasRejectedMember::value) { + return t.rejected; + } else { + return false; + } +} + +#else + +#error This macro must run in compiled mode + +#endif + +using namespace o2::tpc; +using GID = o2::dataformats::GlobalTrackID; +namespace fs = std::filesystem; + +constexpr int NSectors = SECTORSPERSIDE * SIDES; +constexpr int NRows = Mapper::PADROWS; + +// Portable peak-RSS report via getrusage(), which works on both Linux and macOS (unlike parsing +// /proc/self/status, which is Linux-only procfs). ru_maxrss's UNIT differs by platform though (bytes on +// macOS, KB on Linux) -- handled below. Only reports peak resident-set size (one number), not the +// separate VmRSS/VmPeak/VmSize that /proc/self/status exposes on Linux. +void printMemoryUsage(const std::string& label = "") +{ + struct rusage ru; + if (getrusage(RUSAGE_SELF, &ru) != 0) { + return; + } +#ifdef __APPLE__ + const double maxRssGB = ru.ru_maxrss / 1024.0 / 1024.0 / 1024.0; // macOS: ru_maxrss in bytes +#else + const double maxRssGB = ru.ru_maxrss / 1024.0 / 1024.0; // Linux: ru_maxrss in KB +#endif + if (label.empty()) { + LOGP(info, "VmPeakRSS {:.2f} GB", maxRssGB); + } else { + LOGP(info, "[{}] VmPeakRSS {:.2f} GB", label, maxRssGB); + } +} + +template +double calculateMean(const std::vector& vec) +{ + if (vec.empty()) { + LOGP(error, "vector is empty"); + return 0.; + } + + return std::accumulate(vec.begin(), vec.end(), 0.0) / vec.size(); +} + +template +double calculateMedian(std::vector vec) +{ + if (vec.empty()) { + LOGP(error, "vector is empty"); + return 0.; + } + + const size_t size = vec.size(); + const size_t midIndex = size / 2; + + std::nth_element(vec.begin(), vec.begin() + midIndex, vec.end()); + + if (size % 2 != 0) { + return vec[midIndex]; + } + + return (vec[midIndex - 1] + vec[midIndex]) / 2.0; +} + +// Lightweight double-precision 3-vector for the hot per-residual/per-voxel-flush loop, ~10x faster +// than TVector3 here (0.550s -> 0.054s over 20M calls) for numerically identical math -- matters +// because getIntCircles() runs while the per-voxel mutex is held. Double (not float) because this is +// live geometric computation (sqrt- and division-heavy) rather than storage -- contrast Vec3f below, +// which is storage-only and fine in single precision. +struct Vec3d { + double x = 0.0, y = 0.0, z = 0.0; + double X() const { return x; } + double Y() const { return y; } + double Z() const { return z; } + void SetXYZ(double xx, double yy, double zz) + { + x = xx; + y = yy; + z = zz; + } + double Perp() const { return std::sqrt(x * x + y * y); } + double Mag() const { return std::sqrt(x * x + y * y + z * z); } + void RotateZ(double angle) + { + const double c = std::cos(angle), s = std::sin(angle); + const double xn = x * c - y * s, yn = x * s + y * c; + x = xn; + y = yn; + } + Vec3d& operator-=(const Vec3d& o) + { + x -= o.x; + y -= o.y; + z -= o.z; + return *this; + } + Vec3d& operator+=(const Vec3d& o) + { + x += o.x; + y += o.y; + z += o.z; + return *this; + } + Vec3d& operator*=(double a) + { + x *= a; + y *= a; + z *= a; + return *this; + } +}; +inline Vec3d operator-(const Vec3d& a, const Vec3d& b) { return {a.x - b.x, a.y - b.y, a.z - b.z}; } +inline Vec3d operator*(const Vec3d& a, double s) { return {a.x * s, a.y * s, a.z * s}; } + +//------------------------------------------------------------------------------------------------------------ +// Crossing point of two circles in the transverse plane. +// +// Two sentinel returns, both of which callers reject via their own transverse-radius band cut: +// (9999, 9999, 9999) -- the circles do not intersect at all (Perp() far above any accepted radius) +// (0, 0, 0) -- they intersect, but neither solution falls in the valid TPC radial band below +// A third, implicit outcome is NaN: when d lands within a few ulps of a tangency bound (d == r1+r2 or +// d == |r1-r2|) the guards below still pass, but rounding can drive the sqrt argument marginally +// negative, since a == r1 exactly at both bounds in exact arithmetic. NaN then fails the callers' +// band cut too (every comparison against it is false), so such a degenerate pair is simply dropped -- +// which is the wanted behaviour, hence no clamping here. +Vec3d getIntCircles(double r1, double r2, Vec3d circleCenter1, Vec3d circleCenter2, double voxX, double voxY) +{ + Vec3d vecSp; // default-constructed to (0, 0, 0), which is the "no solution accepted" sentinel + + const Vec3d dVec = (circleCenter1 - circleCenter2); + const double d = dVec.Perp(); // dist. circle center 1 & 2 + + if (d > r1 + r2) { + // no solutions, the circles are separate + vecSp.SetXYZ(9999, 9999, 9999); + return vecSp; + } + if (d < std::fabs(r1 - r2)) { + // no solutions, one circle is contained in the other + vecSp.SetXYZ(9999, 9999, 9999); + return vecSp; + } + if (d < 0.0001) { + // no solutions, same circle center + vecSp.SetXYZ(9999, 9999, 9999); + return vecSp; + } + + const double dx = circleCenter2.X() - circleCenter1.X(); + const double dy = circleCenter2.Y() - circleCenter1.Y(); + + const double a = (r1 * r1 - r2 * r2 + d * d) / (2 * d); + const double h = std::sqrt(r1 * r1 - a * a); + + // Midpoint of the chord joining the two intersection points. Deliberately written as a reciprocal + // multiply rather than `a * dx / d`: floating-point reciprocal-then-multiply and multiply-then-divide + // are not guaranteed to give the same last-bit result. Keep this exact form. + const double Mx = circleCenter1.X() + (1 / d) * a * dx; + const double My = circleCenter1.Y() + (1 / d) * a * dy; + + const double sp1x = Mx + h * dy / d; + const double sp2x = Mx - h * dy / d; + + const double sp1y = My - h * dx / d; + const double sp2y = My + h * dx / d; + + // The two solutions take the z of the circle they came from, not a common z. + const double sp1z = circleCenter1.Z(); + const double sp2z = circleCenter2.Z(); + + const double sp1Perp = std::sqrt(sp1x * sp1x + sp1y * sp1y); + const double sp2Perp = std::sqrt(sp2x * sp2x + sp2y * sp2y); + + const bool sp1In = (sp1Perp > 60.0 && sp1Perp < 280.0); + const bool sp2In = (sp2Perp > 60.0 && sp2Perp < 280.0); + + if (sp1In && sp2In) { + // Both solutions are physically plausible -- pick whichever is closer to the voxel center rather + // than an arbitrary, data-independent choice that could just as easily discard the better of two + // valid solutions. + const double d1 = std::sqrt((sp1x - voxX) * (sp1x - voxX) + (sp1y - voxY) * (sp1y - voxY)); + const double d2 = std::sqrt((sp2x - voxX) * (sp2x - voxX) + (sp2y - voxY) * (sp2y - voxY)); + if (d1 <= d2) { + vecSp.SetXYZ(sp1x, sp1y, sp1z); + } else { + vecSp.SetXYZ(sp2x, sp2y, sp2z); + } + } else if (sp1In) { + vecSp.SetXYZ(sp1x, sp1y, sp1z); + } else if (sp2In) { + vecSp.SetXYZ(sp2x, sp2y, sp2z); + } + // else: neither in band, vecSp stays at its default (0,0,0) sentinel + + return vecSp; +} +//------------------------------------------------------------------------------------------------------------ + +struct range { + long from{-1}; + long to{-1}; + + bool operator<(const range& other) + { + return from < other.from; + } + + void sort() + { + if (from > to) { + std::swap(from, to); + } + } +}; + +// ---- Fastest-replica selection for alien:// residual files (used by getInputFileList below) ---- +// Different Storage Elements serving the same LFN can have very different real-world throughput +// depending on where this job actually lands on the network (verified: ALICE::FZK::SE faster than +// ALICE::CERN::EOS from one machine, the reverse from another) -- a name-based heuristic can't predict +// that, so probe with a real timed read instead. Residual files are O(10GB), so getting this wrong once +// costs far more than the probe itself. +struct SEReplica { + std::string se; + std::string pfn; +}; + +std::vector getAlienReplicas(const std::string& plainLFN) +{ + std::vector result; + std::string cmd = "alien.py whereis -r " + plainLFN + " 2>/dev/null"; + std::unique_ptr pipe(popen(cmd.c_str(), "r"), pclose); + if (!pipe) { + return result; + } + std::array buffer; + std::string output; + while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { + output += buffer.data(); + } + + auto trim = [](std::string& s) { + s.erase(0, s.find_first_not_of(" \t")); + s.erase(s.find_last_not_of(" \t\r\n") + 1); + }; + std::istringstream iss(output); + std::string line; + while (std::getline(iss, line)) { + auto sePos = line.find("SE =>"); + auto pfnPos = line.find("pfn =>"); + if (sePos == std::string::npos || pfnPos == std::string::npos) { + continue; + } + std::string se = line.substr(sePos + 5, pfnPos - sePos - 5); + std::string pfn = line.substr(pfnPos + 6); + trim(se); + trim(pfn); + result.push_back({se, pfn}); + } + return result; +} + +// Generates a tiny standalone probe macro on disk (ROOT requires the file stem to match the top-level +// function name) that opens one alien:// replica and reads a few real entries from the 'unbinnedResid' +// tree (the same tree/branch doFileProcessing itself reads, with the same two dominant unused +// sub-branches disabled -- see there), timing the real transfer via TFile::GetBytesRead(). Prints +// "PROBE_OK " or "PROBE_FAIL" to stdout -- this is invoked as a subprocess by +// probeOneReplicaSubprocess below, never called in-process. +std::string writeProbeChildMacro(const std::string& funcName) +{ + static const char* templateSrc = + "#include \n" + "#include \n" + "#include \n" + "#include \n" + "#include \"SpacePoints/TrackInterpolation.h\"\n" + "#include \n" + "#include \n" + "#include \n" + "#include \n" + "#include \n" + "\n" + "void @FUNC@(const char* plainLFN, const char* se, Long64_t probeEntries = 3)\n" + "{\n" + " // This runs in a fresh, separate ROOT process (see probeOneReplicaSubprocess) -- unlike the parent\n" + " // process, gGrid is never already set up here, so it must be connected explicitly.\n" + " if (!gGrid && !TGrid::Connect(\"alien://\")) {\n" + " printf(\"PROBE_FAIL\\n\");\n" + " return;\n" + " }\n" + " std::string url = std::string(\"alien://\") + plainLFN + \"?se=\" + se;\n" + " std::unique_ptr f(TFile::Open(url.c_str(), \"READ\"));\n" + " if (!f || f->IsZombie()) { printf(\"PROBE_FAIL\\n\"); return; }\n" + " TTreeReader reader(\"unbinnedResid\", f.get());\n" + " TTree* residTree = reader.GetTree();\n" + " if (!residTree) { printf(\"PROBE_FAIL\\n\"); return; }\n" + " TTreeReaderValue> res(reader, \"res\");\n" + " residTree->SetBranchStatus(\"res.tgSlp\", 0);\n" + " residTree->SetBranchStatus(\"res.channel\", 0);\n" + "\n" + " Long64_t bytesBefore = f->GetBytesRead();\n" + " auto t0 = std::chrono::steady_clock::now();\n" + " Long64_t nRead = 0;\n" + " for (Long64_t i = 0; i < probeEntries && reader.Next(); ++i) {\n" + " if (static_cast(res.GetSetupStatus()) < 0) break;\n" + " (void)res->size();\n" + " ++nRead;\n" + " }\n" + " auto t1 = std::chrono::steady_clock::now();\n" + " Long64_t bytesRead = f->GetBytesRead() - bytesBefore;\n" + " if (nRead == 0 || bytesRead <= 0) { printf(\"PROBE_FAIL\\n\"); return; }\n" + " double sec = std::chrono::duration(t1 - t0).count();\n" + " printf(\"PROBE_OK %lld %f\\n\", (long long)bytesRead, sec);\n" + "}\n"; + + std::string src(templateSrc); + const std::string placeholder = "@FUNC@"; + auto pos = src.find(placeholder); + if (pos != std::string::npos) { + src.replace(pos, placeholder.size(), funcName); + } + + const std::string path = "/tmp/" + funcName + ".C"; + std::ofstream out(path); + out << src; + out.close(); + return path; +} + +// Probes one candidate replica in a SEPARATE OS PROCESS, bounded by the `timeout` command, so a genuine +// hang (not just a slow-but-working transfer) can be killed without touching this process's own +// TGrid/JAlien connection. An in-process std::async-based timeout was tried and rejected: +// std::future's destructor from std::launch::async BLOCKS until the task finishes regardless of what +// wait_for() returned, so it doesn't actually bound wall-clock time -- and leaking the future to dodge +// that reopens a real concurrent-JAlien-access crash. A real OS process boundary is the only mechanism +// that is both a genuine timeout AND safe against that crash. +// +// Confirmed necessary by a real GRID failure: a job hung completely (~1% CPU for 15 minutes, no +// progress) inside an in-process probe's first candidate until AliEn's idle-CPU watchdog killed the +// whole job. A bounded probe lets it fall through to the next candidate instead of losing the slot. +bool probeOneReplicaSubprocess(const std::string& plainLFN, const std::string& se, double& bytesPerSec, + int timeoutSec = 30, Long64_t probeEntries = 3) +{ + bytesPerSec = -1.0; + const std::string funcName = fmt::format("probeSEChild{}", static_cast(getpid())); + const std::string macroPath = writeProbeChildMacro(funcName); + + const std::string cmd = fmt::format( + "timeout {}s root.exe -b -q -l -x '{}(\"{}\", \"{}\", {})' 2>&1", + timeoutSec, macroPath, plainLFN, se, probeEntries); + + std::string output; + { + std::unique_ptr pipe(popen(cmd.c_str(), "r"), pclose); + if (pipe) { + std::array buffer; + while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { + output += buffer.data(); + } + } + } + std::remove(macroPath.c_str()); + + // Parse line-by-line rather than a single sscanf over the whole output -- ROOT's own startup banner + // and "Info in " lines precede the actual PROBE_OK/PROBE_FAIL line. + std::istringstream iss(output); + std::string line; + while (std::getline(iss, line)) { + long long bytesRead = 0; + double sec = 0.0; + if (sscanf(line.c_str(), "PROBE_OK %lld %lf", &bytesRead, &sec) == 2) { + if (sec > 0 && bytesRead > 0) { + bytesPerSec = static_cast(bytesRead) / sec; + return true; + } + return false; + } + if (line.rfind("PROBE_FAIL", 0) == 0) { + return false; + } + } + return false; // empty/no matching line -- timed out (killed by `timeout`) or crashed +} + +// Probes each candidate replica in turn (via probeOneReplicaSubprocess above) and returns the SE with +// the highest measured throughput, or "" if every candidate failed or timed out. +std::string probeFastestSE(const std::string& plainLFN, const std::vector& replicas, + Long64_t probeEntries = 3, int timeoutSec = 30) +{ + std::string bestSE; + double bestBytesPerSec = -1.0; + for (const auto& r : replicas) { + double bps = -1.0; + if (!probeOneReplicaSubprocess(plainLFN, r.se, bps, timeoutSec, probeEntries)) { + LOGP(warning, "SE probe: {} failed or timed out after {}s", r.se, timeoutSec); + continue; + } + LOGP(info, "SE probe: {} -> {:.1f} MB/s", r.se, bps / (1024.0 * 1024.0)); + if (bps > bestBytesPerSec) { + bestBytesPerSec = bps; + bestSE = r.se; + } + } + return bestSE; +} + +std::vector loadRunTimeSpans(const std::string& flname, int onlyRun, const std::string& selection); +std::vector getInputFileList(const std::string& fileInput) +{ + std::vector fileList; + std::vector fileListVerified; + // check if only one input file (a txt file contaning a list of files is provided) + if (fileInput.length() > 3 && fileInput.substr(fileInput.length() - 3, 3) == "txt") { + LOGP(info, "Reading files from input file list {}", fileInput); + std::ifstream is(fileInput); + std::istream_iterator start(is); + std::istream_iterator end; + fileList.insert(fileList.begin(), start, end); + } else { + fileList.push_back(fileInput); + } + + // fastestSE is probed once, for the first alien:// file, and reused for the rest of this slot's files + // (same production run -> same SE set, in practice), avoiding a ~10GB-file probe cost per file. If a + // later file isn't actually hosted on the cached SE, doFileProcessing's own open has a fallback to + // unforced resolution -- so a stale cache can't silently drop a file, only cost a little speed for + // that one file. + std::string fastestSE; + for (auto file : fileList) { + if ((file.find("alien://") == 0) && !gGrid && !TGrid::Connect("alien://")) { + LOGP(fatal, "Failed to open alien connection"); + } + if (gSystem->Getenv("FORCESE") && !TString(file.data()).EndsWith(gSystem->Getenv("FORCESE"))) { + file += "?se="; + file += gSystem->Getenv("FORCESE"); + } else if (!gSystem->Getenv("FORCESE") && file.rfind("alien://", 0) == 0) { + if (fastestSE.empty()) { + std::string plainLFN = file.substr(std::string("alien://").size()); + auto slash = plainLFN.find_first_not_of('/'); + plainLFN = (slash == std::string::npos) ? "/" : "/" + plainLFN.substr(slash); + auto replicas = getAlienReplicas(plainLFN); + if (!replicas.empty()) { + fastestSE = (replicas.size() == 1) ? replicas.front().se : probeFastestSE(plainLFN, replicas); + if (!fastestSE.empty()) { + LOGP(info, "Auto-selected fastest SE {} for this slot's alien:// files", fastestSE); + } + } + } + if (!fastestSE.empty()) { + file += "?se="; + file += fastestSE; + } + } + fileListVerified.push_back(file); + } + + if (fileListVerified.size() == 0) { + LOGP(error, "No input files to process"); + } + return fileListVerified; +} + +bool revalidateTrack(const TrackData& trk, const SpacePointsCalibConfParam& params) +{ + + if (hasPositiveFilterFlag(trk)) { + return false; + } + + if (fabs(trk.par.getTgl()) > params.maxZ2X) { + return false; + } + if (trk.nClsITS < params.minITSNCls) { + return false; + } + if (trk.nClsTPC < params.minTPCNCls) { + return false; + } + // No TRD-based cuts here (neither on the tracklet count nor on chi2TRD): this macro does not use TRD. + // track quality cuts + if (trk.chi2ITS / trk.nClsITS > params.maxITSChi2) { + return false; + } + if (trk.chi2TPC / trk.nClsTPC > params.maxTPCChi2) { + return false; + } + + if (params.cutOnDCA) { + auto propagator = o2::base::Propagator::Instance(); + // o2::track::TrackPar trkPar(trk.x, trk.alpha, trk.p); // use this line, in case ClassDef version of TrackData < 4 + o2::track::TrackPar trkPar = trk.par; + if (!propagator->propagateToX(trkPar, 0, propagator->getNominalBz())) { + return false; + } + if (trkPar.getX() * trkPar.getX() + trkPar.getY() * trkPar.getY() > params.maxDCA * params.maxDCA) { + return false; + } + } + return true; +} + +// Check that a TTreeReaderValue was actually bound to a branch of the expected type. +// Unlike SetBranchAddress (which silently tolerated a missing/mismatching branch), dereferencing a +// TTreeReaderValue that failed to set up returns a null proxy and segfaults, so this has to be +// checked once per file before the data is used. The setup status is only final after the first +// entry has been loaded, so call this after SetEntry(). +template +bool checkReaderValue(const TTreeReaderValue& value, const int iThread, const std::string& fileName) +{ + // all failure codes of ESetupStatus are negative (kSetupMatch is 0, the other success codes are positive) + const auto status = value.GetSetupStatus(); + if (static_cast(status) < 0) { + LOGP(warning, "[Thread{}] Branch '{}' could not be set up (setup status {}) in file {}", iThread, value.GetBranchName(), static_cast(status), fileName); + return false; + } + return true; +} + +// Pool size per voxel/charge. Compile-time rather than a runtime parameter so that circleCenters and +// circleRadii below can be fixed-size std::array instead of heap-allocated std::vector: with ~3M +// voxels in a realistic bin configuration, per-voxel heap allocations add up to roughly 4.2 GB of +// resident memory and 12M+ small mallocs for these two members alone. Changing the pool size means +// editing this line and recompiling. +constexpr int NPool = 15; + +// Warm-up threshold for the no-input-map DZ rolling-average correction: minimum cumulative NP/PP/NN +// sample count (see VoxelData below) required before a voxel's residualsAll[0] (dX) is trusted enough +// to shift the Z propagation. See the call site in doFileProcessing for the full reasoning. +constexpr int MinDxSamplesForZCorr = 20; + +// Storage for circleCenters: pure scratch coordinates (never serialized/drawn), populated from +// float-precision inputs (xycircle.xC/yC, track/voxel positions) in the first place, so float is +// enough -- consistent with circleRadii already being float. Converts implicitly to Vec3d at the point +// of use (getIntCircles computes in double internally regardless of the float input). +struct Vec3f { + float x = 0.f, y = 0.f, z = 0.f; + operator Vec3d() const { return Vec3d{x, y, z}; } +}; + +// shared circle pool for one voxel; one instance per voxel (not per thread), guarded by its own mutex +struct VoxelData { + std::mutex mtx; //! per-voxel mutex, shared across threads + std::array, 2> circleCenters; // [charge] was vec_TV3_circle_center_thread + std::array, 2> circleRadii; // [charge] was vec_TV3_circle_radius_thread + std::array poolCounter{}; // [charge] was vec_counter_thread + + std::array residualsAll{}; // was vec_residualsAll_thread; [2] (Z) is a running sum, see counterZAll + std::array residualsNP{}; // was vec_residualsNP_thread + std::array residualsPP{}; // was vec_residualsPP_thread + std::array residualsNN{}; // was vec_residualsNN_thread + int counterNP = 0; // was vec_residuals_counterNP_thread + int counterPP = 0; // was vec_residuals_counterPP_thread + int counterNN = 0; // was vec_residuals_counterNN_thread + int counterZAll = 0; // was vec_residuals_counterZAll_thread +}; + +// One TimeFrame's data, fully OWNED (copied out of the TTreeReaderValues rather than referencing them). +// TTreeReaderValue::operator* reuses the same underlying storage on every SetEntry -- a background +// producer thread reading TF N+1 into that storage while the consumer is still processing TF N's tracks +// would race and corrupt data, the same class of hazard already found once in this file (a lazy- +// deserialization race across concurrent track-worker threads). Copying the three vectors out per TF +// avoids that; the copy itself is small next to the per-TF track-processing time. +struct TFPackage { + int iEntry = 0; + std::vector trackRefsVec; + std::vector trackDataVec; + std::vector unbinnedResidualsVec; +}; + +// Bounded single-producer/single-consumer queue of TFPackages. Lets one background thread stay a few +// TimeFrames ahead of the (I/O-free -- see doFileProcessing's own track-loop-parallelism notes) track +// processing, so a TTreeCache refill on some later TF can overlap with the current TF's track-worker +// compute instead of blocking it. This is meant to hide the periodic TTreeCache-refill spikes this +// pipeline's I/O shows in practice, and only pays off paired with a moderate TTreeCache size -- too +// large a cache makes individual refills bigger than any reasonable queue depth can absorb. +class BoundedTFQueue +{ + public: + explicit BoundedTFQueue(size_t maxDepth) : mMaxDepth(maxDepth) {} + + // Producer side. Blocks while the queue is already at capacity. + void push(std::unique_ptr pkg) + { + std::unique_lock lock(mMtx); + mNotFull.wait(lock, [this] { return mQueue.size() < mMaxDepth; }); + mQueue.push_back(std::move(pkg)); + lock.unlock(); + mNotEmpty.notify_one(); + } + + // Producer side, called once (file fully read, or the maxTracks quota was reached). + void setDone() + { + { + std::lock_guard lock(mMtx); + mDone = true; + } + mNotEmpty.notify_one(); + } + + // Consumer side. Returns nullptr once the producer is done AND the queue has been fully drained. + std::unique_ptr pop() + { + std::unique_lock lock(mMtx); + mNotEmpty.wait(lock, [this] { return !mQueue.empty() || mDone; }); + if (mQueue.empty()) { + return nullptr; + } + auto pkg = std::move(mQueue.front()); + mQueue.pop_front(); + lock.unlock(); + mNotFull.notify_one(); + return pkg; + } + + // Diagnostic only: how many packages are sitting ready right now. Lets the consumer log whether the + // producer is comfortably ahead (queue usually near mMaxDepth) or struggling to keep up (queue usually + // near empty) -- distinguishes "the buffer is the wrong depth" from "the producer itself is too slow + // to ever fill it, no matter how deep it is". + size_t size() const + { + std::lock_guard lock(mMtx); + return mQueue.size(); + } + + private: + const size_t mMaxDepth; + mutable std::mutex mMtx; + std::condition_variable mNotEmpty; + std::condition_variable mNotFull; + std::deque> mQueue; + bool mDone = false; +}; + +// How many TimeFrames the background producer may stay ahead of track-processing. Absorbs some of the +// periodic TTreeCache-refill spikes this pipeline's I/O shows in practice, though 10 (the default +// below) isn't enough to fully hide the biggest ones -- a much larger depth would be needed for that, +// at a real memory cost (a single TF can carry tens of thousands of tracks). Overridable via +// SCDCALIB_TF_QUEUE_DEPTH (no recompile) to tune against a real workload's spike size. +constexpr size_t TFQueueDepthDefault = 10; + +void doFileProcessing(const int iThread, + const int nFileThreads, + const int maxTrackWorkers, + const long firstTFTime, + const long lastTFTime, + const bool invertBadRange, + const float maxdEdx, + const float maxdEdxExp, + const float maxDevdEdxOverExp, + const float skipEdgePads, + std::vector& nEdgeClustersSkipped_thread, + std::vector& nTracksSkippedByBadRangeList_thread, + std::vector& nTFs_thread, + std::vector& nTFsSkippedByBadRangeList_thread, + std::vector& nTFsSkippedByTimeWindow_thread, + const std::string voxMapInput, + const GID::mask_t sources, + const int64_t orbitResetTimeMS, + const float magfieldvalue, + const std::vector fileList, + const int maxTracksPerSlice, + const Long64_t maxTracks, + std::atomic& nTracksProcessed, + const std::array, NSectors>& voxelResults, // read-only input correction map, shared across threads (no per-thread copy) + std::vector>& badRanges_thread, + const TrackResiduals& trackResiduals, // read-only: findVoxelBin/getVoxelCoordinates/getGlbVoxBin are all const, safe to share across threads + const float maxDistIntCls, + const int nY2XBins, + const int nZ2XBins, + std::vector& voxels, // flat [sec*152*nY2XBins*nZ2XBins + ix*nY2XBins*nZ2XBins + iy*nZ2XBins + iz] -- shared across threads, one instance per voxel + std::vector& totalBytesReadPerf_thread, + std::vector& lumiEntriesCTP_thread, + std::vector& lumiSumCTP_thread, + std::vector>& orbitsSel_thread, + std::vector>& ctpLumiSel_thread, + std::vector>& timeMSsel_thread) +{ + // Get Mapper + const Mapper& mapper = Mapper::instance(); + + // yMaxCentrePadByRow only depends on the pad row (152 values) -- precomputed once per file-thread + // here, gated on skipEdgePads since that's its only use, rather than via a Mapper lookup per residual. + std::array yMaxCentrePadByRow{}; + if (skipEdgePads) { + for (int irow = 0; irow < NRows; ++irow) { + yMaxCentrePadByRow[irow] = mapper.getPadCentre(o2::tpc::PadPos(irow, 0)).Y() - mapper.getPadRegionInfo(o2::tpc::Mapper::REGION[irow]).getPadWidth() / 2; + } + } + + // Obtain configuration per thread + const SpacePointsCalibConfParam& params_thread = SpacePointsCalibConfParam::Instance(); + + // Per-thread input handles and I/O-monitoring state. These are plain locals: each file-thread only ever + // touches its own, so there is nothing to share with the other threads or hand back to the caller. + // + // DECLARATION ORDER IS LOAD-BEARING. Locals are destroyed in reverse order of declaration, and these + // objects reference each other: a TTreeReaderValue refers to its TTreeReader, which refers to a TTree + // owned by the TFile, and TTreePerfStats refers to that tree too. Declaring the file first and the + // reader values last therefore tears them down in the only safe order -- values, then perf stats, then + // readers, then the file. Do not reorder these. + std::unique_ptr inputFile; + std::unique_ptr treeUnbinnedResiduals; + std::unique_ptr treeTrackData; + std::unique_ptr treeRecords; + std::unique_ptr perfStats; + std::unique_ptr>> unbinnedResiduals; // unbinned residuals input + std::unique_ptr>> trackRefs; // track references for the unbinned residuals + std::unique_ptr>> trackData; // additional track info (chi2, nClusters, track parameters) + std::unique_ptr>> orbits; // first orbit of each TF in the input data + std::unique_ptr> lumiTF; // lumi info + + // Previous I/O sample, for the instantaneous-rate log inside the TF loop. + Long64_t perfLastBytes = 0; + std::chrono::steady_clock::time_point perfLastSample; + + int trackCounter_local{0}; + + // Track-loop parallelism, WITHIN this one file-thread only -- active when nFileThreads==1 (GRID/ + // alien:// mode, forced by getInputFileList() for TGrid safety) or when there's only one input file + // (otherwise every other core would sit idle). Safe because the track/cluster loop body touches no + // TGrid/CCDB/file I/O (SetEntry() already happened before this point), only in-memory TF data plus the + // same per-voxel mutex (vox.mtx) multi-file-thread mode already relies on. Each worker gets its own + // copy of every piece of mutated state (RNG, scratch coordinates, counters) -- sharing any of it + // across workers would be a silent data race. Otherwise nTrackWorkers is forced to 1 (serial, via + // worker index 0) to avoid oversubscribing on top of the file-threads. + int nTrackWorkers = 1; + if (nFileThreads == 1 || fileList.size() == 1) { + if (maxTrackWorkers > 0) { + // Explicit override (e.g. the number of cores actually allocated to a batch/GRID job). + // hardware_concurrency() reports the machine's core count, not the job's allocation -- an 8-core + // GRID job auto-detects 32 and oversubscribes 4x -- so when the caller knows, trust it instead. + nTrackWorkers = maxTrackWorkers; + } else { + unsigned hc = std::thread::hardware_concurrency(); + nTrackWorkers = (hc == 0) ? 8 : static_cast(hc); + if (nTrackWorkers > 32) { + nTrackWorkers = 32; + } + } + } + LOGP(info, "[Thread_{}] Using {} track-worker thread(s) for the track loop", iThread, nTrackWorkers); + std::vector clsPosWorker(nTrackWorkers); + // Per-worker scratch: this track's position at the current row. + std::vector trackPosAtRow_worker(nTrackWorkers); + std::vector nEdgeClustersSkipped_worker(nTrackWorkers, 0); + std::vector trackCounter_local_worker(nTrackWorkers, 0); + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // ===| FILE LOOP |=================================================================================================================== + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // This thread's share of the input files, as an explicit work list: this is what lets a file that + // looks unhealthy be pushed to the BACK and retried later instead of being processed now or dropped -- + // see the health checks below for why deferring beats both. Appending while iterating by index is + // safe: the bound is re-read every iteration and no iterators are held. + std::vector workList; + for (int i = iThread; i < int(fileList.size()); i += nFileThreads) { + workList.push_back(i); + } + // How many times each file has already been deferred, so a persistently sick one cannot cycle forever. + std::vector deferCount(fileList.size(), 0); + int maxFileDeferrals = 1; + if (const char* envMaxDeferrals = gSystem->Getenv("SCDCALIB_MAX_FILE_DEFERRALS")) { + maxFileDeferrals = std::atoi(envMaxDeferrals); + } + // Health-probe timings (seconds) of the files accepted so far, used as this job's own baseline. An + // absolute threshold cannot work here: measured healthy timings differ by ~6x between GRID regimes, + // so what matters is how a file compares to the others in ITS OWN slot, not to a constant. + std::vector probeTimes; + double probeSlowFactor = 5.0; + if (const char* envSlowFactor = gSystem->Getenv("SCDCALIB_PROBE_SLOW_FACTOR")) { + probeSlowFactor = std::atof(envSlowFactor); + } + // Fallback used until enough samples exist for a median to mean anything (and if the very first file + // of a slot is the sick one, this is the only thing standing between us and the stall). + double probeAbsMaxSec = 15.0; + if (const char* envProbeAbsMax = gSystem->Getenv("SCDCALIB_PROBE_ABS_MAX_SEC")) { + probeAbsMaxSec = std::atof(envProbeAbsMax); + } + // Floor under the median-relative threshold once >=3 samples exist. Without it, a fast-regime median + // (tens of ms) makes ordinary network jitter look "5x slower than usual" and trip the probe -- and a + // file unlucky enough to trip it twice is dropped for good (see deferFile below), losing real data to + // noise. The probe's actual target is stalls an order of magnitude bigger (~20s against a ~1.5s + // baseline, in the real case this was built around), so a small floor can't mask that while still + // absorbing sub-second jitter in a fast regime. + double probeMinThresholdSec = 2.0; + if (const char* envProbeMinThreshold = gSystem->Getenv("SCDCALIB_PROBE_MIN_THRESHOLD_SEC")) { + probeMinThresholdSec = std::atof(envProbeMinThreshold); + } + + for (size_t iWork = 0; iWork < workList.size(); ++iWork) { + const int iFile = workList[iWork]; + // Get filename from fileList + auto fileName = fileList[iFile]; + + // Check if enough tracks are processed + if ((maxTracks > 0) && (nTracksProcessed.load(std::memory_order_relaxed) > maxTracks)) { + LOGP(info, "[Thread_{}] Maximum number of requested tracks processed {} > {} ({}), will not process further files", iThread, nTracksProcessed.load(std::memory_order_relaxed), maxTracks, maxTracksPerSlice); + break; + } + + if (gSystem->Getenv("FORCESE") && !TString(fileName.data()).EndsWith(gSystem->Getenv("FORCESE"))) { + fileName += "?se="; + fileName += gSystem->Getenv("FORCESE"); + } + + // Open tree and set branches + LOGP(info, "[Thread{}] Processing input file {}", iThread, fileName); + unbinnedResiduals.reset(nullptr); + trackRefs.reset(nullptr); + lumiTF.reset(nullptr); + trackData.reset(nullptr); + orbits.reset(nullptr); + // Must be destroyed here, before the readers/file below: TTreePerfStats registers itself as the + // process-wide gPerfStats global and keeps raw TFile*/TTree* pointers to the file it watches. + // Leaving it alive past this point means gPerfStats still points at this (about to be destroyed) + // file while the NEXT file's setup does real reads -- TFile::ReadBuffer() unconditionally calls + // gPerfStats->FileReadEvent(thatFile, ...), which compares thatFile against the dangling fFile + // pointer; if the allocator hands the new TFile the same address the old one was just freed from + // (a real, common allocator pattern for same-sized immediate reuse), the comparison spuriously + // matches and it dereferences the also-dangling fTree -- a real, reproduced segfault on the 2nd+ + // file of a multi-file run, verified against the installed ROOT's TTreePerfStats.cxx/TFile.cxx + // source. perfStats's own destructor is safe to call here (only clears gPerfStats if it's still the + // registered one; never dereferences fTree/fFile), so this alone fixes it. + perfStats.reset(nullptr); + treeUnbinnedResiduals.reset(nullptr); + treeTrackData.reset(nullptr); + treeRecords.reset(nullptr); + + const auto openStart = std::chrono::steady_clock::now(); + inputFile.reset(TFile::Open(fileName.c_str())); + if ((!inputFile || inputFile->IsZombie())) { + // A forced-SE URL (FORCESE or the auto-picked/cached fastest SE, see getInputFileList) can fail if + // this particular file isn't actually hosted there -- fall back to unforced alien:// resolution + // rather than skipping the file outright, since a stale SE cache would otherwise silently drop data. + auto sePos = fileName.find("?se="); + if (sePos != std::string::npos) { + std::string fallbackName = fileName.substr(0, sePos); + LOGP(warning, "[Thread_{}] Forced-SE open failed for {}, retrying without SE override: {}", iThread, fileName, fallbackName); + inputFile.reset(TFile::Open(fallbackName.c_str())); + } + } + // Gates the "[prefetch diag]" per-TF/per-file I/O diagnostics further below: real signal for + // diagnosing GRID stalls/CPU-idle-watchdog kills (the reason this machinery exists at all), but + // pure noise on a local/Lustre run with many file-threads where none of that risk applies -- a + // 36-file-thread local run was producing an unreadable flood of per-TF consumer lines otherwise. + const bool isAlienFile = fileName.find("alien://") != std::string::npos; + if (isAlienFile) { + if (inputFile && !inputFile->IsZombie()) { + inputFile->SetBufferSize(4000000); + LOGP(info, "[Thread_{}] Set buffer size to {}", iThread, inputFile->GetBufferSize()); + } else { + LOGP(info, "[Thread_{}] TFile {} is empty", iThread, fileName); + } + } + + if (!inputFile || inputFile->IsZombie()) { + LOGP(warning, "[Thread{}] Skipping file {}", iThread, fileName); + continue; + } + + // Deferring an unhealthy-looking file, rather than skipping it. Observed for real: a file that was + // reproducibly slow in one job read at full speed a short time later -- the slowness is transient + // storage-server state, not a property of the file. So dropping it outright throws away data that + // would very likely have been fine. Pushing it to the back of this thread's work list costs exactly + // what skipping costs right now, but gives the server time to recover, and if maxTracks stops the + // job first the file is never touched again at all. The caller always moves on to the next file. + auto deferFile = [&](const char* reason, double measured, double threshold) { + if (deferCount[iFile] < maxFileDeferrals) { + ++deferCount[iFile]; + workList.push_back(iFile); + LOGP(warning, "[Thread{}] {} for {} ({:.1f} s vs {:.1f} s threshold) -- deferring it to the end of this thread's file list (attempt {} of {}); storage slowness has been seen to be transient, so it may read fine later, and maxTracks may stop the job before we return to it", + iThread, reason, fileName, measured, threshold, deferCount[iFile], maxFileDeferrals); + return; + } + LOGP(warning, "[Thread{}] {} for {} ({:.1f} s vs {:.1f} s threshold) and it has already been deferred {} time(s) -- skipping this file for good", + iThread, reason, fileName, measured, threshold, deferCount[iFile]); + }; + + // --- Unhealthy-replica gate ------------------------------------------------------------------- + // An abnormally slow TFile::Open is the earliest sign a replica's storage server is struggling, + // and the last point we can walk away cheaply: the warm-up read right after this pulls a whole + // TTreeCache-sized chunk in one uninterruptible call, so if the server stalls there the job hangs + // until AliEn's ~15-minute idle-CPU watchdog kills it, discarding all work already done. Skipping + // one file here costs a fraction of a slot's statistics, so the trade is heavily one-sided. 15 s + // threshold, calibrated against real GRID opens (absolute, not relative to this job's own timings + // -- may need revisiting on very different links). Tune via SCDCALIB_MAX_FILE_OPEN_SEC; <= 0 + // disables it. + double maxFileOpenSec = 15.0; + if (const char* envMaxOpenSec = gSystem->Getenv("SCDCALIB_MAX_FILE_OPEN_SEC")) { + maxFileOpenSec = std::atof(envMaxOpenSec); + } + const double openSec = std::chrono::duration(std::chrono::steady_clock::now() - openStart).count(); + if (maxFileOpenSec > 0 && openSec > maxFileOpenSec) { + deferFile("Slow file open (SCDCALIB_MAX_FILE_OPEN_SEC)", openSec, maxFileOpenSec); + continue; + } + + treeUnbinnedResiduals = std::make_unique("unbinnedResid", inputFile.get()); + if (!treeUnbinnedResiduals->GetTree()) { + LOGP(warning, "[Thread{}] Could not get tree 'unbinnedResid' from file {}. Skipping file!", iThread, fileName); + continue; + } + + // GetEntries() only reads TTree header metadata, not branch data -- cheap even on alien://, unlike + // the TTreeCache/branch setup further below. Fetched here (still before that setup) so the fail-fast + // time-window check below can run before anything expensive touches the network. + const auto nTFEntries = treeUnbinnedResiduals->GetEntries(); + if (nTFEntries <= 0) { + LOGP(warning, "[Thread{}] Tree 'unbinnedResid' in file {} has no entries. Skipping file!", iThread, fileName); + continue; + } + + treeRecords = std::make_unique("records", inputFile.get()); + if (!treeRecords->GetTree()) { + LOGP(warning, "[Thread{}] Could not get tree 'records' from file {}. Skipping file!", iThread, fileName); + continue; + } + orbits = std::make_unique>>(*treeRecords, "firstTForbit"); + // Real data has exactly one entry in 'records', but MC input can have several (e.g. one per + // simulation chunk merged into this file) -- each entry's own 'firstTForbit' only covers that + // chunk's TFs, so reading just entry 0 silently truncated the orbit list on MC, tripping the + // length check below and skipping the whole file. Concatenate every entry's vector instead, in + // entry order, to rebuild the same flat, TF-index-ordered list this file's real-data path already + // produced from its single entry (verified for real: MC input observed with 5 'records' entries). + std::vector combinedOrbits; + { + const Long64_t nRecordsEntries = treeRecords->GetEntries(); + bool recordsOk = true; + for (Long64_t ie = 0; ie < nRecordsEntries; ++ie) { + if (treeRecords->SetEntry(ie) != TTreeReader::kEntryValid || + !checkReaderValue(*orbits, iThread, fileName)) { + recordsOk = false; + break; + } + const auto& thisEntryOrbits = **orbits; + combinedOrbits.insert(combinedOrbits.end(), thisEntryOrbits.begin(), thisEntryOrbits.end()); + } + if (!recordsOk) { + LOGP(warning, "[Thread{}] Could not load the orbits from tree 'records' in file {}. Skipping file!", iThread, fileName); + continue; + } + } + // the orbit list is indexed with the TF index of the unbinnedResid tree below, so it has to be at least as long + if (static_cast(combinedOrbits.size()) < nTFEntries) { + LOGP(error, "[Thread{}] 'firstTForbit' has fewer entries than the residual tree has TFs ({} vs {}) in file {}. Skipping file!", iThread, + combinedOrbits.size(), nTFEntries, fileName); + continue; + } + + // Set timeStamp for processing, get this file's [min,max] orbit-derived time range, and find the + // first TF entry actually inside the requested window -- all in one pass over the (already + // downloaded) 'firstTForbit' array. Bounded to the first nTFEntries entries: orbits can have extra + // trailing entries beyond what the residual tree actually has (see the size check above) that don't + // correspond to any real TF and must not feed either the fail-fast check below or the warm-up entry. + uint32_t minFirstOrbit = -1; + uint32_t maxFirstOrbit = 0; + Long64_t warmupEntry = 0; + bool foundWarmupEntry = (firstTFTime <= 0); // no time filter set: entry 0 is always fine to warm up on + { + const auto& orbitsVec = combinedOrbits; + for (Long64_t i = 0; i < nTFEntries; ++i) { + const uint32_t orbit = orbitsVec[i]; + if (orbit < minFirstOrbit) { + minFirstOrbit = orbit; + } + if (orbit > maxFirstOrbit) { + maxFirstOrbit = orbit; + } + if (!foundWarmupEntry) { + const int64_t t = orbitResetTimeMS + orbit * o2::constants::lhc::LHCOrbitMUS * 1.e-3; + if (t >= firstTFTime && t <= lastTFTime) { + warmupEntry = i; + foundWarmupEntry = true; + } + } + } + } + // ---| Fail fast on files with zero overlap with the requested time window |--- + if (firstTFTime > 0) { + const int64_t fileMinTimeMS = orbitResetTimeMS + minFirstOrbit * o2::constants::lhc::LHCOrbitMUS * 1.e-3; + const int64_t fileMaxTimeMS = orbitResetTimeMS + maxFirstOrbit * o2::constants::lhc::LHCOrbitMUS * 1.e-3; + if (fileMaxTimeMS < firstTFTime || fileMinTimeMS > lastTFTime) { + LOGP(warning, "[Thread{}] File {} has no TF inside the requested time window [{}, {}] ms (file spans [{}, {}] ms) -- skipping the whole file without downloading its residual tree", + iThread, fileName, firstTFTime, lastTFTime, fileMinTimeMS, fileMaxTimeMS); + // Counted as if the per-TF loop below had visited and skipped each entry, so nTFs stays a + // consistent denominator for the skip-fraction summary at the end. + nTFs_thread[iThread] += nTFEntries; + nTFsSkippedByTimeWindow_thread[iThread] += nTFEntries; + continue; + } + } + + // --- Read-health probe, BEFORE the TTreeCache is enabled -------------------------------------- + // A slow open alone can miss a replica that opens fine but stalls on the first real read, so this + // probes a small read directly, before SetCacheSize/AddBranchToCache below turn the first read into + // a whole cache-sized fetch that can hang with nothing able to interrupt it. Probes 'trackData' (a + // small member-split branch) rather than the already-read 'records' tree, which sits next to the + // file header and reads fast regardless of whether the replica then stalls on real data. Uses + // TBranch::GetEntry directly rather than a TTreeReader, to avoid creating a second reader on a tree + // that gets its real one further below. A missing branch skips the probe rather than failing it -- + // this is a health check, not a validity check. + { + TTree* probeTree = dynamic_cast(inputFile->Get("trackData")); + TBranch* probeBranch = probeTree ? probeTree->GetBranch("trk.nClsTPC") : nullptr; + if (probeBranch && probeTree->GetEntries() > 0) { + const Long64_t probeEntry = std::min(warmupEntry, probeTree->GetEntries() - 1); + const auto probeStart = std::chrono::steady_clock::now(); + const Int_t probeBytes = probeBranch->GetEntry(probeEntry); + const double probeSec = std::chrono::duration(std::chrono::steady_clock::now() - probeStart).count(); + + // If the read returned nothing, the measurement says nothing about the replica's health -- fall + // through to the normal path rather than judging the file on it. Deliberately fail-open: a probe + // that cannot measure must not be able to reject files, or an unexpected branch layout would + // quietly defer every file in the slot. + if (probeBytes <= 0) { + LOGP(info, "[Thread{}] Read-health probe returned no data for {} (entry {}) -- skipping the health check for this file", iThread, fileName, probeEntry); + } else { + // Baseline: the median probe time of files already accepted in this slot. Below 3 samples a + // median is meaningless, so fall back to the absolute guard. + double probeThreshold = probeAbsMaxSec; + if (probeTimes.size() >= 3) { + std::vector sorted(probeTimes); + std::nth_element(sorted.begin(), sorted.begin() + sorted.size() / 2, sorted.end()); + const double median = sorted[sorted.size() / 2]; + probeThreshold = std::max(probeSlowFactor * median, probeMinThresholdSec); + } + if (probeThreshold > 0 && probeSec > probeThreshold) { + deferFile("Slow read probe (SCDCALIB_PROBE_SLOW_FACTOR/SCDCALIB_PROBE_ABS_MAX_SEC)", probeSec, probeThreshold); + continue; + } + // Only healthy files feed the baseline, so one sick file cannot raise the bar for the next. + probeTimes.push_back(probeSec); + } + } + } + + // I/O throughput monitor -- large cache so async prefetch has room to read many baskets ahead; only + // branches actually accessed get cached/prefetched. TTreePerfStats records raw bytes read from the + // remote file, so its rate is the actual download speed. + // + // 256 MB, not 512 MB: a real GRID job hit an 11+ minute stall refilling a single 512MB chunk (~0.7 + // MB/s vs. 45-100 MB/s for every other chunk on the same SE), then a second stall that never + // recovered, losing the whole job to AliEn's idle-CPU watchdog. A smaller chunk halves the + // worst-case single-chunk wait and lets a persistently slow file be abandoned sooner -- a + // reliability trade-off against 512MB's better throughput (~17% vs ~13% wall-clock reduction) when + // nothing is stalling. Overridable via SCDCALIB_CACHE_SIZE_MB. + { + TTree* residTree = treeUnbinnedResiduals->GetTree(); + int dbgCacheSizeMB = 256; + if (const char* envCacheSizeMB = gSystem->Getenv("SCDCALIB_CACHE_SIZE_MB")) { + dbgCacheSizeMB = std::atoi(envCacheSizeMB); + } + if (isAlienFile) { + LOGP(info, "[Thread{}] [prefetch diag] TTreeCache size = {} MB (SCDCALIB_CACHE_SIZE_MB, default 256)", iThread, dbgCacheSizeMB); + } + residTree->SetCacheSize(static_cast(dbgCacheSizeMB) * 1024 * 1024); + residTree->AddBranchToCache("*", true); + perfStats = std::make_unique(fmt::format("ioperf_{}_{}", iThread, iFile).data(), residTree); + } + perfLastBytes = 0; + perfLastSample = std::chrono::steady_clock::now(); + + unbinnedResiduals = std::make_unique>>(*treeUnbinnedResiduals, "res"); + trackRefs = std::make_unique>>(*treeUnbinnedResiduals, "trackInfo"); + lumiTF = std::make_unique>(*treeUnbinnedResiduals, "CTPLumi"); + + // Skip reading UnbinnedResid/TrackDataCompact members that are never used below. 'res' and + // 'trackInfo' are member-split branches, one sub-branch per struct field, and the unused ones + // dominate the file (res.tgSlp alone can be >1 GB in a single input file), so disabling them means + // they are never transferred at all -- measured ~17% fewer bytes read. Must come AFTER the + // TTreeReaderValues above so that it is the final word on these sub-branches' status. + // Do not disable res.dy/dz/y/z/row/sec/rejected or trackInfo.idxFirstResidual/nResiduals/sourceId -- + // all of those are read below. Note trackInfo.filterFlag (on TrackDataCompact) is unused, while the + // separate trk.filterFlag (on TrackData, read further down) is used; they are different fields. + { + TTree* residTree = treeUnbinnedResiduals->GetTree(); + for (const char* br : {"res.tgSlp", "res.channel", "trackInfo.multStack*", + "trackInfo.nExtDetResid", "trackInfo.filterFlag"}) { + residTree->SetBranchStatus(br, 0); + } + } + + // Load one entry once so the reader values get bound, then verify all branches are there before + // anything below dereferences them. Warms up on warmupEntry (computed above, the first entry + // actually inside the requested time window) rather than always entry 0 -- for a file only partially + // overlapping the window, entry 0 is often outside it, and fetching its residual data would be + // exactly the kind of wasted network read the whole-file fail-fast check above targets, just at the + // scale of one TF. + if (treeUnbinnedResiduals->SetEntry(warmupEntry) != TTreeReader::kEntryValid) { + LOGP(warning, "[Thread{}] Could not load entry {} of 'unbinnedResid' from file {}. Skipping file!", iThread, warmupEntry, fileName); + continue; + } + if (!checkReaderValue(*unbinnedResiduals, iThread, fileName) || + !checkReaderValue(*trackRefs, iThread, fileName) || + !checkReaderValue(*lumiTF, iThread, fileName)) { + LOGP(warning, "[Thread{}] Skipping file {}", iThread, fileName); + continue; + } + + // Re-prime the reader values after the pre-scan (which leaves the reader past the last entry), + // so a stale-read (e.g. the bad-range-skip stats below) at warmupEntry dereferences valid data. + if (treeUnbinnedResiduals->SetEntry(warmupEntry) != TTreeReader::kEntryValid) { + LOGP(warning, "[Thread{}] Could not re-load entry {} of 'unbinnedResid' from file {}. Skipping file!", iThread, warmupEntry, fileName); + continue; + } + + { + treeTrackData = std::make_unique("trackData", inputFile.get()); + if (!treeTrackData->GetTree()) { + LOGP(warning, "[Thread{}] Could not get tree 'trackData' from file {}. Skipping file!", iThread, fileName); + continue; + } + { + TTree* trackDataTree = treeTrackData->GetTree(); + for (const char* br : {"trk.gid*", "trk.chi2TRD", "trk.deltaTOF", "trk.nTrkltsTRD", + "trk.clAvailTOF", "trk.TRDTrkltSlope*", "trk.nExtDetResid", + "trk.clIdx.*", "trk.multStack*"}) { + trackDataTree->SetBranchStatus(br, 0); + } + } + trackData = std::make_unique>>(*treeTrackData, "trk"); + if (treeTrackData->GetEntries() != nTFEntries) { + LOGP(error, "[Thread{}] The input trees with unbinned residuals and track information have a different number of entries ({} vs {}). Skipping file!", iThread, + nTFEntries, treeTrackData->GetEntries()); + continue; + } + // Same TF indexing as 'unbinnedResid' -- warm up on the same entry for the same reason (see above). + if (treeTrackData->SetEntry(warmupEntry) != TTreeReader::kEntryValid || + !checkReaderValue(*trackData, iThread, fileName)) { + LOGP(warning, "[Thread{}] Skipping file {}", iThread, fileName); + continue; + } + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // ===| TIME FRAME LOOP |============================================================================================================= + // Split into a background PRODUCER thread (skip-checks, SetEntry/warm-up, sync check, lumi/orbit + // bookkeeping, and reading each TF's data) pushing TFPackages into a bounded queue, and this thread + // as CONSUMER, popping a package and dispatching the track workers on it -- see BoundedTFQueue/ + // TFPackage above for why. The producer is the only thread that ever touches + // treeUnbinnedResiduals/treeTrackData/the TTreeReaderValues (exactly one thread doing TGrid/file I/O + // at a time); the consumer never touches them at all, only the packages it pops. + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + size_t tfQueueDepth = TFQueueDepthDefault; + if (const char* envQueueDepth = gSystem->Getenv("SCDCALIB_TF_QUEUE_DEPTH")) { + tfQueueDepth = static_cast(std::atoi(envQueueDepth)); + } + if (isAlienFile) { + LOGP(info, "[Thread{}] [prefetch diag] TFQueueDepth = {} (SCDCALIB_TF_QUEUE_DEPTH, default {})", iThread, tfQueueDepth, TFQueueDepthDefault); + } + BoundedTFQueue tfQueue(tfQueueDepth); + + // Set by the consumer below when a single tfQueue.pop() waited unreasonably long, checked by the + // producer between TF reads (never inside one -- see the long comment at the consumer's check site + // for why). Assumes the fetch has become persistently slow rather than truly dead: a producer stuck + // forever never reaches this check at all and would need a separate, not-yet-built process-level + // watchdog -- this only shortens the "slow but eventually returns" case, not a genuine hang. + std::atomic abandonFile{false}; + double popWaitAbandonMs = 60000.0; // 60s -- matches the scale of the real stalls that motivated this + if (const char* envAbandonMs = gSystem->Getenv("SCDCALIB_POP_WAIT_ABANDON_MS")) { + popWaitAbandonMs = std::atof(envAbandonMs); + } + + std::thread producerThread([&]() { + // A failed SetEntry() (checked below) only means the tree could not be repositioned -- it says + // nothing about whether a given lazily-read branch's basket actually arrived. TTreeReaderValue + // fetches each branch on its own first dereference for the current entry, and a mid-stream storage + // hiccup there doesn't throw: it prints a ROOT error and leaves the underlying object in an + // unspecified state, which then segfaults wherever it's first used with no indication of why + // (observed for real: an xrootd "Operation expired" mid basket-read, immediately followed by a + // SIGSEGV with only the ROOT error line as a clue). Must be called right after the value's first + // dereference for this entry -- GetReadStatus() reports the status of that specific read. + auto checkReadOk = [&](ROOT::Internal::TTreeReaderValueBase& val, const char* branchName, int iEntry) { + if (val.GetReadStatus() != ROOT::Internal::TTreeReaderValueBase::kReadSuccess) { + LOGP(warning, "[Thread{}] Storage read error on branch '{}' at entry {} of file {} (GetReadStatus={}) -- skipping TF!", + iThread, branchName, iEntry, fileName, static_cast(val.GetReadStatus())); + return false; + } + return true; + }; + for (int iEntry = 0; iEntry < nTFEntries; ++iEntry) { + // Checked here, between TF reads, never inside one -- this point is only ever reached right + // after the previous push() succeeded, so the producer is never mid-call when this fires. See + // the consumer's check site for the full reasoning. + if (abandonFile.load(std::memory_order_relaxed)) { + LOGP(warning, "[Thread{}] Abandoning the rest of file {} ({}/{} TFs read) after a persistently slow fetch", iThread, fileName, iEntry, nTFEntries); + break; + } + ++nTFs_thread[iThread]; + + // Periodic I/O throughput progress log. Now reports the producer's own progress through the + // file, which can run ahead of what the consumer has actually finished processing. + if (iEntry % 50 == 0) { + double instMBps = 0.0, totMB = 0.0; + if (perfStats) { + const auto nowSample = std::chrono::steady_clock::now(); + const double dt = std::chrono::duration(nowSample - perfLastSample).count(); + const Long64_t br = perfStats->GetBytesRead(); + instMBps = (dt > 0) ? (br - perfLastBytes) / (1024.0 * 1024.0) / dt : 0.0; + totMB = br / (1024.0 * 1024.0); + perfLastBytes = br; + perfLastSample = nowSample; + } + const Long64_t nProcessedNow = nTracksProcessed.load(std::memory_order_relaxed); + if (maxTracks > 0) { + LOGP(info, "[Thread{}] TF entry {}/{} | read {:.1f} MB, inst. {:.1f} MB/s | tracks processed {}/{} ({:.1f}%)", + iThread, iEntry, nTFEntries, totMB, instMBps, nProcessedNow, maxTracks, 100.0 * nProcessedNow / maxTracks); + } else { + LOGP(info, "[Thread{}] TF entry {}/{} | read {:.1f} MB, inst. {:.1f} MB/s | tracks processed {} (no limit set)", + iThread, iEntry, nTFEntries, totMB, instMBps, nProcessedNow); + } + } + + // Check if enough tracks are processed + if ((maxTracks > 0) && (nTracksProcessed.load(std::memory_order_relaxed) > maxTracks)) { + LOGP(info, "[Thread{}] Maximum number of requested tracks processed {} > {} ({}), will not process further TFs", iThread, nTracksProcessed.load(std::memory_order_relaxed), maxTracks, maxTracksPerSlice); + break; + } + + // ---| check for TF time acceptance |--- + const int64_t tfTimeInMS = orbitResetTimeMS + combinedOrbits[iEntry] * o2::constants::lhc::LHCOrbitMUS * 1.e-3; + if ((firstTFTime > 0) && (tfTimeInMS < firstTFTime || tfTimeInMS > lastTFTime)) { + if (nTFsSkippedByTimeWindow_thread[iThread] == 0) { + // Log once per thread rather than per TF: this can legitimately fire for every TF of every + // file, e.g. when the requested [firstTFTime,lastTFTime] window contains no data at all + // because a time-slice boundary landed past the end of the run. A per-TF log would then + // produce one line per TF for the entire job. + LOGP(warning, "[Thread{}] TF at index {} (time {} ms, orbit {}) outside requested window [{}, {}] ms -- skipping (will keep happening silently for further TFs outside the window, see final summary for the total count)", + iThread, iEntry, tfTimeInMS, combinedOrbits[iEntry], firstTFTime, lastTFTime); + } + ++nTFsSkippedByTimeWindow_thread[iThread]; + continue; + } + // ---| check for time exclusion list |--- + if (badRanges_thread[iThread].size() > 0) { + bool skip = false; + for (const auto& range : badRanges_thread[iThread]) { + if ((combinedOrbits[iEntry] >= range.from) && (combinedOrbits[iEntry] <= range.to)) { + skip = true; + break; + } + } + if (invertBadRange) { + skip = !skip; + } + if (skip) { + nTracksSkippedByBadRangeList_thread[iThread] += (*trackRefs)->size(); + ++nTFsSkippedByBadRangeList_thread[iThread]; + continue; + } + } + if (params_thread.timeFilter) { + if (tfTimeInMS < params_thread.startTimeMS || tfTimeInMS > params_thread.endTimeMS) { + continue; + } + } + + // --- [prefetch diag]: producer-side read/deserialize timing, kept to confirm the periodic-spike + // I/O pattern still looks the same underneath the producer/consumer split -- expected to be + // mostly hidden from the consumer by TFQueueDepth, not eliminated. --- + const auto dbgTIoStart = std::chrono::steady_clock::now(); + + // ---| Read entries |--- + if (treeUnbinnedResiduals->SetEntry(iEntry) != TTreeReader::kEntryValid) { + LOGP(warning, "[Thread{}] Could not load entry {} of 'unbinnedResid' from file {}. Skipping TF!", iThread, iEntry, fileName); + continue; + } + if (treeTrackData->SetEntry(iEntry) != TTreeReader::kEntryValid) { + LOGP(warning, "[Thread{}] Could not load entry {} of 'trackData' from file {}. Skipping TF!", iThread, iEntry, fileName); + continue; + } + + // First dereference of 'trackInfo' for this entry -- triggers the actual branch read; must be + // checked before .size() (or anything else) trusts the result, see checkReadOk above. + (void)(**trackRefs); + if (!checkReadOk(*trackRefs, "trackInfo", iEntry)) { + continue; + } + const auto nTracks = (*trackRefs)->size(); + + // Materialize this TF's 'res' branch here, on the producer thread, before it's copied out below. + // TTreeReaderValue::operator* deserializes lazily on the first dereference per entry. + (void)(**unbinnedResiduals).size(); + if (!checkReadOk(*unbinnedResiduals, "res", iEntry)) { + continue; + } + + const double dbgIoMs = std::chrono::duration(std::chrono::steady_clock::now() - dbgTIoStart).count(); + { + thread_local double dbgSumIoMs = 0.0; + thread_local uint64_t dbgNProduced = 0; + dbgSumIoMs += dbgIoMs; + ++dbgNProduced; + if (isAlienFile && dbgNProduced % 10 == 0) { + LOGP(info, "[Thread{}] [prefetch diag][producer] TF {} nTracks={} ioMs={:.1f} | running: {} TF(s) read, sumIoMs={:.0f}", + iThread, iEntry, nTracks, dbgIoMs, dbgNProduced, dbgSumIoMs); + } + } + + // First dereference of 'trackData' for this entry -- same lazy-read/checkReadOk requirement as + // 'trackInfo'/'res' above. + (void)(**trackData); + if (!checkReadOk(*trackData, "trackData", iEntry)) { + continue; + } + + // the track loop below indexes trackData with the trackRefs index, so both have to be in sync + if ((**trackData).size() < nTracks) { + LOGP(warning, "[Thread{}] TF {} of file {} has fewer track data entries than track references ({} vs {}). Skipping TF!", iThread, iEntry, fileName, + (**trackData).size(), nTracks); + continue; + } + + lumiSumCTP_thread[iThread] += (*lumiTF)->getLumi(); + ++lumiEntriesCTP_thread[iThread]; + + timeMSsel_thread[iThread].emplace_back(tfTimeInMS); + orbitsSel_thread[iThread].emplace_back((*lumiTF)->orbit); + ctpLumiSel_thread[iThread].emplace_back((*lumiTF)->getLumi()); + + // Copy the three vectors out (see TFPackage) and hand the package to the consumer. push() blocks + // here if the queue is already at TFQueueDepth, which is exactly the back-pressure that keeps the + // producer from running arbitrarily far ahead. + auto pkg = std::make_unique(); + pkg->iEntry = iEntry; + pkg->trackRefsVec = **trackRefs; + pkg->trackDataVec = **trackData; + pkg->unbinnedResidualsVec = **unbinnedResiduals; + tfQueue.push(std::move(pkg)); + } + tfQueue.setDone(); + }); + + while (true) { + const auto dbgTPopStart = std::chrono::steady_clock::now(); + std::unique_ptr pkg = tfQueue.pop(); + const double dbgPopWaitMs = std::chrono::duration(std::chrono::steady_clock::now() - dbgTPopStart).count(); + const size_t dbgQueueSizeAfterPop = tfQueue.size(); // how many the producer still has ready right now + + // A pop() this slow only ever returns *after* the producer's own push() for this package already + // succeeded -- i.e. the producer is guaranteed to be between TF reads right now, not blocked + // inside one, so signalling it here can never race a live TFile/TGrid call. Real motivation: a + // real GRID job had one file's chunk refill alone take 11+ minutes (~0.7 MB/s vs. 45-100 MB/s for + // every other chunk on the same SE), then a second chunk on the same file never returned and the + // whole job was killed by AliEn's idle-CPU watchdog. This won't catch that second, truly-dead case + // (the producer never reaches this check then -- needs a separate process-level watchdog, not yet + // built), but it does mean a merely very slow file gets abandoned after one bad chunk instead of + // risking a permanent stall. + if (pkg && dbgPopWaitMs > popWaitAbandonMs && !abandonFile.load(std::memory_order_relaxed)) { + LOGP(warning, "[Thread{}] popWait {:.0f} ms exceeds abandon threshold {:.0f} ms (SCDCALIB_POP_WAIT_ABANDON_MS) -- signalling the producer to give up on the rest of this file, assuming a persistently slow fetch rather than a dead one", + iThread, dbgPopWaitMs, popWaitAbandonMs); + abandonFile.store(true, std::memory_order_relaxed); + } + if (!pkg) { + break; // producer is done and the queue is drained + } + const int iEntry = pkg->iEntry; + const auto nTracks = pkg->trackRefsVec.size(); + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // ===| TRACK LOOP |================================================================================================================== + // Body extracted into a per-track lambda so it can be dispatched across nTrackWorkers compute + // threads (see the setup + rationale above the FILE LOOP). A `return` inside this lambda skips to + // the next track -- the lambda body IS one track's worth of work. The cluster loop's own + // `continue`s still mean what they always do: that for-loop lives inside the lambda unchanged. + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + auto processTrack = [&](size_t iTrack, int iWorker) { + const auto& trkInfo = pkg->trackRefsVec[iTrack]; + if (!GID::includesSource(trkInfo.sourceId, sources)) { + return; + } + const auto& trk = pkg->trackDataVec[iTrack]; + if (!revalidateTrack(trk, params_thread)) { + return; + } + + auto propagator = o2::base::Propagator::Instance(); + o2::track::TrackPar trkPar = trk.par; + int sign = trkPar.getSign(); + + int charge = 0; + if (sign > 0) { + charge = 1; + } + + // dE/dx cut + if (maxdEdx > 0 && trk.dEdxTPC > maxdEdx) { + return; + } + + if (maxdEdxExp > 0 || maxDevdEdxOverExp > 0) { + // propagate to the beginning of the inner containment vessel, to use the momentum for dE/dx expected + if (!propagator->PropagateToXBxByBz(trkPar, 63.2, 0.99, 2., o2::base::Propagator::MatCorrType::USEMatCorrLUT)) { // USEMatCorrTGeo, USEMatCorrLUT, USEMatCorrNONE + return; + } + + const auto dEdxExp = o2::track::BetheBlochSolidOpt(trk.par.getP() / trk.par.getPID().getMass()) * 3e4; + + if (maxdEdxExp > 0 && dEdxExp > maxdEdxExp) { + return; + } + + if (maxDevdEdxOverExp > 0 && std::abs(trk.dEdxTPC / dEdxExp - 1) > maxDevdEdxOverExp) { + return; + } + } + if (!propagator->PropagateToXBxByBz(trkPar, 85., 0.99, 2., o2::base::Propagator::MatCorrType::USEMatCorrLUT)) { // USEMatCorrTGeo, USEMatCorrLUT, USEMatCorrNONE + return; + } + + // INCREASE LOCAL TRACK COUNTER + ++trackCounter_local_worker[iWorker]; + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // ===| CLUSTER & RESIDUAL LOOP |===================================================================================================== + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + for (unsigned int i = trkInfo.idxFirstResidual; i < trkInfo.idxFirstResidual + trkInfo.nResiduals; ++i) { + const auto& residIn = pkg->unbinnedResidualsVec[i]; + int sec = residIn.sec; + if (residIn.row >= NRows || sec >= NSectors || sec < 0) { // non TPC residuals have row>=160, though to see them one should loop until i<(trc.clIdx.getFirstEntry() + trc.clIdx.getEntries() + trc.nExtDetResid) + continue; + } + + if (isRejectedResidual(residIn)) { + continue; + } + + float angleSec = TMath::DegToRad() * (10.0 + 20.0 * sec); + std::array bvox; + // cluster position + float xPos = param::RowX[residIn.row]; + float yPos = residIn.y * param::MaxY / 0x7fff + residIn.dy * param::MaxResid / 0x7fff; + float zPos = residIn.z * param::MaxZ / 0x7fff + residIn.dz * param::MaxResid / 0x7fff; + // exclude the edge pads as they are biased! + // get max y-position of edge pad: pad centre last pad - pad width/2 + if (skipEdgePads && std::abs(yPos) > yMaxCentrePadByRow[residIn.row]) { + ++nEdgeClustersSkipped_worker[iWorker]; + continue; + } + + clsPosWorker[iWorker].SetXYZ(xPos, yPos, zPos); + if (!trackResiduals.findVoxelBin(sec, xPos, yPos, zPos, bvox)) { + // we are not inside any voxel + continue; + } + + // circle pool for this voxel is shared across threads -> lock for the rest of this iteration + auto& vox = voxels[((sec * NRows + bvox[2]) * nY2XBins + bvox[1]) * nZ2XBins + bvox[0]]; + std::unique_lock voxLock(vox.mtx); + + // XALEX + if (vox.poolCounter[charge] == NPool) { + continue; + } + + //--------------------------------------------------------- + // Main part of the new code + float xposvox, yoverxpos, zoverxpos; + trackResiduals.getVoxelCoordinates(sec, bvox[2], bvox[1], bvox[0], xposvox, yoverxpos, zoverxpos); + if (fabs(xposvox) < 5.0) { + continue; + } + float yposvox = yoverxpos * xposvox; + float zposvox = zoverxpos * xposvox; + + float dxclsvoxel = clsPosWorker[iWorker].X() - xposvox; + float dyclsvoxel = clsPosWorker[iWorker].Y() - yposvox; + float dzclsvoxel = clsPosWorker[iWorker].Z() - zposvox; + + Vec3d deltaClsVoxel; + deltaClsVoxel.SetXYZ(dxclsvoxel, dyclsvoxel, dzclsvoxel); + + //----------------------------------------- + trkPar.rotate(o2::math_utils::sector2Angle(sec)); + + propagator->PropagateToXBxByBz(trkPar, xPos, 0.99, 2., o2::base::Propagator::MatCorrType::USEMatCorrLUT); // USEMatCorrTGeo, USEMatCorrLUT, USEMatCorrNONE + trackPosAtRow_worker[iWorker].SetXYZ(trkPar.getX(), trkPar.getY(), trkPar.getZ()); + + float sna, csa; + o2::math_utils::CircleXY::value_t> xycircle; + trkPar.getCircleParams(magfieldvalue, xycircle, sna, csa); // in global coordinates + + Vec3d circleCenterEstimate; + circleCenterEstimate.SetXYZ(xycircle.xC, xycircle.yC, 0.0); + circleCenterEstimate.RotateZ(-angleSec); + float radius_estimate = xycircle.rC; + + if ((trackPosAtRow_worker[iWorker] - clsPosWorker[iWorker]).Perp() > maxDistIntCls) { + continue; + } + + //----------------------------------------- + // DeltaZ corrections, two methods + if (voxMapInput.size()) // with input map from first itteration, should be more precise than second method + { + // we already have a correction map available + const auto& voxRes = voxelResults[sec][trackResiduals.getGlbVoxBin(bvox)]; // bvox: z,y,x + float DX_input_map = voxRes.D[TrackResiduals::ResX]; + + propagator->PropagateToXBxByBz(trkPar, xPos - DX_input_map, 0.99, 2., o2::base::Propagator::MatCorrType::USEMatCorrLUT); // USEMatCorrTGeo, USEMatCorrLUT, USEMatCorrNONE + float DZdist = (zPos - trkPar.getZ()); // distortion + // accumulate Z sum (shared across threads for this voxel, guarded by vox.mtx) and increment counter + vox.residualsAll[2] += DZdist; + vox.counterZAll++; + } else { + // without input map, use rolling average -- gate on the cumulative NP/PP/NN counters (never + // reset across flushes, unlike poolCounter) so this only fires once residualsAll[0] has + // actually been computed from a decent number of samples, not merely whenever the in-flight + // pool for this charge happens to be non-empty (poolCounter resets to 0 on every flush, so + // checking it here both under- and over-fires relative to residualsAll[0]'s real validity). + if (vox.counterNP > MinDxSamplesForZCorr || vox.counterPP > MinDxSamplesForZCorr || vox.counterNN > MinDxSamplesForZCorr) { + float DXrollingaverage = vox.residualsAll[0]; + propagator->PropagateToXBxByBz(trkPar, xPos - DXrollingaverage, 0.99, 2., o2::base::Propagator::MatCorrType::USEMatCorrLUT); // USEMatCorrTGeo, USEMatCorrLUT, USEMatCorrNONE + float DZdist = (zPos - trkPar.getZ()); // distortion + vox.residualsAll[2] += DZdist; + vox.counterZAll++; + } + } + //----------------------------------------- + + circleCenterEstimate -= deltaClsVoxel; // shift track to voxel center to avoid smearing within voxel + + int counter = vox.poolCounter[charge]; + vox.circleCenters[charge][counter] = Vec3f{static_cast(circleCenterEstimate.X()), static_cast(circleCenterEstimate.Y()), static_cast(circleCenterEstimate.Z())}; + vox.circleRadii[charge][counter] = radius_estimate; + vox.poolCounter[charge]++; + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // ===| PROCESSING VOXEL |============================================================================================================ + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + if ((vox.poolCounter[0] >= NPool || vox.poolCounter[1] >= NPool)) { + //////////////////////////////// + // Same polarity combinations // + //////////////////////////////// + float weightCurvatureInt = 0.0; + Vec3d averageInt; + averageInt.SetXYZ(0.0, 0.0, 0.0); + + for (int counterPos = 0; counterPos < (vox.poolCounter[1] - 1); counterPos++) { + for (int counterPosB = (counterPos + 1); counterPosB < vox.poolCounter[1]; counterPosB++) { + float radiusA = vox.circleRadii[1][counterPos]; + float radiusB = vox.circleRadii[1][counterPosB]; + + if (fabs((radiusA - radiusB) / (radiusA + radiusB)) < 0.2) { + continue; // to similar bending radii + } + + Vec3d intCircles = getIntCircles(radiusA, radiusB, vox.circleCenters[1][counterPos], vox.circleCenters[1][counterPosB], xposvox, yposvox); + if (intCircles.Perp() < 280.0 && intCircles.Perp() > 60.0) { + float distIntCls = (intCircles - clsPosWorker[iWorker]).Perp(); // why? + if (distIntCls > maxDistIntCls) { + continue; + } + + float weight = fabs((radiusA - radiusB) / (radiusA + radiusB)); + float weight_curvature = weight; + + weightCurvatureInt += weight_curvature; + averageInt += intCircles * weight_curvature; + } + } + } + + if (weightCurvatureInt > 0) { + averageInt *= 1.0 / weightCurvatureInt; + + // distortion + // accumulate PP sums (x,y) and increment counter + vox.residualsPP[0] += (xposvox - averageInt.X()); + vox.residualsPP[1] += (yposvox - averageInt.Y()); + vox.counterPP++; + } + + weightCurvatureInt = 0.0; + averageInt.SetXYZ(0.0, 0.0, 0.0); + + for (int counterNeg = 0; counterNeg < (vox.poolCounter[0] - 1); counterNeg++) { + for (int counterNegB = (counterNeg + 1); counterNegB < vox.poolCounter[0]; counterNegB++) { + float radiusA = vox.circleRadii[0][counterNeg]; + float radiusB = vox.circleRadii[0][counterNegB]; + + if (fabs((radiusA - radiusB) / (radiusA + radiusB)) < 0.2) { + continue; // to similar bending radii + } + + Vec3d intCircles = getIntCircles(radiusA, radiusB, vox.circleCenters[0][counterNeg], vox.circleCenters[0][counterNegB], xposvox, yposvox); + if (intCircles.Perp() < 280.0 && intCircles.Perp() > 60.0) { + float distIntCls = (intCircles - clsPosWorker[iWorker]).Perp(); // why? + if (distIntCls > maxDistIntCls) { + continue; + } + + float weight = fabs((radiusA - radiusB) / (radiusA + radiusB)); + float weight_curvature = weight; + + weightCurvatureInt += weight_curvature; + averageInt += intCircles * weight_curvature; + } + } + } + + if (weightCurvatureInt > 0) { + averageInt *= 1.0 / weightCurvatureInt; + + // distortion + // accumulate NN sums (x,y) and increment counter + vox.residualsNN[0] += (xposvox - averageInt.X()); + vox.residualsNN[1] += (yposvox - averageInt.Y()); + vox.counterNN++; + } + + //////////////////////////////////// + // Opposite polarity combinations // + //////////////////////////////////// + weightCurvatureInt = 0.0; + averageInt.SetXYZ(0.0, 0.0, 0.0); + + for (int counterPos = 0; counterPos < vox.poolCounter[1]; counterPos++) { + for (int counterNeg = 0; counterNeg < vox.poolCounter[0]; counterNeg++) { + float radiusA = vox.circleRadii[1][counterPos]; + float radiusB = vox.circleRadii[0][counterNeg]; + Vec3d intCircles = getIntCircles(radiusA, radiusB, vox.circleCenters[1][counterPos], vox.circleCenters[0][counterNeg], xposvox, yposvox); + if (intCircles.Perp() < 280.0 && intCircles.Perp() > 60.0) { + float distIntCls = (intCircles - clsPosWorker[iWorker]).Perp(); // why? + if (distIntCls > maxDistIntCls) { + continue; + } + + // float weight_curvature = (1.0/radiusA)*(1.0/radiusB); // the larger the curvature the more precise the intersection can be calculated + // float weight_curvature = (radiusA)*(radiusB); // the larger the curvature the more precise the intersection can be calculated + + float weight_curvature = 1.0; // (1.0/radiusA)*(1.0/radiusB); + + weightCurvatureInt += weight_curvature; + averageInt += intCircles * weight_curvature; + } + } + } + + if (weightCurvatureInt > 0) { + averageInt *= 1.0 / weightCurvatureInt; + + // distortion + // accumulate NP sums (x,y) and increment counter + vox.residualsNP[0] += (xposvox - averageInt.X()); + vox.residualsNP[1] += (yposvox - averageInt.Y()); + vox.counterNP++; + } + + float weightNP = vox.counterNP * 1.0; + float weightPP = vox.counterPP * 0.01; + float weightNN = vox.counterNN * 0.01; + + float sum_weight = weightNP + weightPP + weightNN; + + if (sum_weight > 0.0f) { + // convert sums to means before weighting + float meanNPx = (vox.counterNP > 0) ? (vox.residualsNP[0] / static_cast(vox.counterNP)) : 0.0f; + float meanNPy = (vox.counterNP > 0) ? (vox.residualsNP[1] / static_cast(vox.counterNP)) : 0.0f; + float meanPPx = (vox.counterPP > 0) ? (vox.residualsPP[0] / static_cast(vox.counterPP)) : 0.0f; + float meanPPy = (vox.counterPP > 0) ? (vox.residualsPP[1] / static_cast(vox.counterPP)) : 0.0f; + float meanNNx = (vox.counterNN > 0) ? (vox.residualsNN[0] / static_cast(vox.counterNN)) : 0.0f; + float meanNNy = (vox.counterNN > 0) ? (vox.residualsNN[1] / static_cast(vox.counterNN)) : 0.0f; + + vox.residualsAll[0] = (weightNP * meanNPx + weightPP * meanPPx + weightNN * meanNNx) / sum_weight; + vox.residualsAll[1] = (weightNP * meanNPy + weightPP * meanPPy + weightNN * meanNNy) / sum_weight; + } + + // reset the pools: only the counter is reset. circleCenters/circleRadii are fixed-size + // std::array now (not std::vector) -- there's no clear()/resize() to even call; + // stale entries beyond the new poolCounter are simply overwritten as the pool refills. + for (int ic = 0; ic < 2; ic++) { + vox.poolCounter[ic] = 0; + } + } + //--------------------------------------------------------- + + // // update COG for voxel bvox (update for X only needed in case binning is not per pad row) + + } // end of cluster loop + }; // end of processTrack lambda + + const auto dbgTCpuStart = std::chrono::steady_clock::now(); + if (nTrackWorkers <= 1) { + for (size_t iTrack = 0; iTrack < nTracks; ++iTrack) { + processTrack(iTrack, 0); + } + } else { + std::vector trackThreads; + trackThreads.reserve(nTrackWorkers); + for (int iWorker = 0; iWorker < nTrackWorkers; ++iWorker) { + trackThreads.emplace_back([&, iWorker]() { + for (size_t iTrack = iWorker; iTrack < nTracks; iTrack += nTrackWorkers) { + processTrack(iTrack, iWorker); + } + }); + } + for (auto& th : trackThreads) { + th.join(); + } + } + const double dbgCpuMs = std::chrono::duration(std::chrono::steady_clock::now() - dbgTCpuStart).count(); + { + // [prefetch diag][consumer]: the number that actually matters. dbgPopWaitMs is how long this + // thread blocked in tfQueue.pop() waiting for the producer -- with the pipeline working, this + // should be near zero most of the time (the producer stays ahead), spiking only when it can't + // keep up (e.g. a cache-refill spike bigger than TFQueueDepth's cushion). + thread_local double dbgSumPopWaitMs = 0.0; + thread_local double dbgSumCpuMs = 0.0; + thread_local uint64_t dbgNTFs = 0; + dbgSumPopWaitMs += dbgPopWaitMs; + dbgSumCpuMs += dbgCpuMs; + ++dbgNTFs; + // Unlike the producer log above, this one had no sampling gate at all -- printed every single + // TF, on every thread, which is the dominant source of "[prefetch diag]" log volume. Matched to + // the producer's every-10th cadence (the cumulative sum* fields lose nothing from sampling) and + // gated on isAlienFile for the same reason as the producer log. + if (isAlienFile && dbgNTFs % 10 == 0) { + LOGP(info, "[Thread{}] [prefetch diag][consumer] TF {} nTracks={} popWaitMs={:.1f} cpuMs={:.1f} queueSizeAfterPop={} | running totals over {} TF(s): sumPopWaitMs={:.0f} sumCpuMs={:.0f}", + iThread, iEntry, nTracks, dbgPopWaitMs, dbgCpuMs, dbgQueueSizeAfterPop, dbgNTFs, dbgSumPopWaitMs, dbgSumCpuMs); + } + } + // Merge this TF's per-worker counters into the file-thread-level counters the rest of + // doFileProcessing (and the caller's merge loop, for nEdgeClustersSkipped_thread) expect. + for (int iWorker = 0; iWorker < nTrackWorkers; ++iWorker) { + trackCounter_local += trackCounter_local_worker[iWorker]; + trackCounter_local_worker[iWorker] = 0; + nEdgeClustersSkipped_thread[iThread] += nEdgeClustersSkipped_worker[iWorker]; + nEdgeClustersSkipped_worker[iWorker] = 0; + } + } // end of TF loop (consumer) + + // producerThread only ever reaches here via tfQueue.setDone() (end of file or maxTracks reached), + // which the consumer's pop() == nullptr check above already waited for -- this join is therefore + // just reclaiming the thread, not a real wait. Must happen before perfStats->Finish()/Print() below, + // since perfStats is only ever touched by the producer thread. + producerThread.join(); + + if (perfStats) { + perfStats->Finish(); + if (isAlienFile) { + perfStats->Print(); // full TTreePerfStats I/O report (incl. "Disk IO = ... MBytes/s") -- GRID-diagnostic only, see isAlienFile above + } + totalBytesReadPerf_thread[iThread] += perfStats->GetBytesRead(); + } + + // Occasionally flush local count to global + if (trackCounter_local >= 1000) { + nTracksProcessed.fetch_add(trackCounter_local, std::memory_order_relaxed); + trackCounter_local = 0; + } + + } // end of file loop + + if (trackCounter_local > 0) { + nTracksProcessed.fetch_add(trackCounter_local, std::memory_order_relaxed); + } +} + +void staticMapCreatorCPM(std::string fileInput = "files.txt", + int runNumber = 527976, + std::string fileOutput = "voxRes.root", + std::string voxMapInput = "", + std::string trackSources = static_cast(GID::ALL), + std::string z2xBinning = "", // empty: default binning; single number: uniform binning; otherwise bin boundaries, e.g. "0.,0.02, 0.04, 0.06, 1" + std::string y2xBinning = "", // empty: default binning; single number: uniform binning; otherwise bin boundaries, e.g. "-1,-0.998, -0.996, ... 0.996, 0.998, 1" + bool useSmoothed = true, // use smoothed residuals as input + bool createSpline = true, // create the splines + int maxTracksPerSlice = -1, // limit the number of total tracks processed to maxTracksPerSlice * nBinsZ2X * nBinxY2X * 36 + int minTracksPerSlice = -1, // request a minimum number of tracks per slice. Otherwise the calibration is not created + std::string badRangeList = "", // list of bad time ranges to be excluded in the calibration + long firstTFTime = -1, // First TF time to accept + long lastTFTime = -1, // Last TF time to accept + float maxdEdx = -1, // dE/dx cut above which tracks will be rejected + float maxdEdxExp = -1, // dE/dx expected cut above which tracks will be rejected + float maxDevdEdxOverExp = -1, // maximum deviation of dE/dx / expected value, above the track is rejected + bool skipEdgePads = 1, // skip edge pads in the calibration, by default on + std::string badRangeSelection = "ALL", // use bad time ranges only for specific comment e.g. C1 + float maxZ2XCut = 1.f, // overrides scdcalib.maxZ2X (the track tgl cut applied in revalidateTrack). + // Defaults to the SpacePointsCalibConfParam compiled-in value of 1.0 + int maxTrackWorkers = -1, // number of threads used for the track loop within one file. <=0 (default) + // auto-detects via hardware_concurrency(), which reports the machine's core + // count rather than the cores actually allocated to a batch job -- pass the + // real allocation explicitly when running on a batch system or the GRID + int nThreads = 8) // number of file-level threads, i.e. how many input files are processed + // concurrently. Forced to 1 for alien:// input regardless, since TGrid is + // not thread-safe (see below) +{ + LOGP(info, "TrackData::filterFlag {}, UnbinnedResid::rejected {}", + HasFilterFlagMember::value ? "available" : "NOT available (old O2, cut skipped)", + HasRejectedMember::value ? "available" : "NOT available (old O2, cut skipped)"); + + // Enable multiple threads + ROOT::EnableThreadSafety(); + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // ==== TIME =============================================================================================================== + auto t_start = std::chrono::high_resolution_clock::now(); + // ========================================================================================================================= + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + fair::Logger::SetVerbosity(fair::Verbosity::medium); + fair::Logger::SetConsoleSeverity(fair::Severity::info); + fair::Logger::SetFileSeverity(fair::Severity::info); + + const Mapper& mapper = Mapper::instance(); + + // Obtain configuration + const SpacePointsCalibConfParam& params = SpacePointsCalibConfParam::Instance(); + if (!std::filesystem::exists("scdconfig.ini")) { + LOGP(warning, "Did not find configuration file. Using default parameters and storing them in scdconfig.ini"); + params.writeINI("scdconfig.ini", "scdcalib"); // to write default parameters to a file + } else { + params.updateFromFile("scdconfig.ini"); + } + // Explicit override from the macro's own argument, applied AFTER any scdconfig.ini load so it wins + // regardless of whether one happens to exist in CWD -- see maxZ2XCut's doc comment above. Using the + // string-based setValue overload (implemented out-of-line in ConfigurableParam.cxx), not the + // templated one -- that one needs boost::property_tree fully instantiated at the call site, which + // isn't included here. + o2::conf::ConfigurableParam::setValue("scdcalib.maxZ2X", std::to_string(maxZ2XCut)); + LOGP(info, "----- Dumping configuration values START -----"); + params.printKeyValues(); + LOGP(info, "----- Dumping configuration values END -----"); + + GID::mask_t allowedSources = GID::getSourcesMask("ITS-TPC,ITS-TPC-TRD,ITS-TPC-TOF,ITS-TPC-TRD-TOF"); + GID::mask_t sources = allowedSources & GID::getSourcesMask(trackSources); + + // Get CCDB objects + auto& ccdbmgr = o2::ccdb::BasicCCDBManager::instance(); + ccdbmgr.setCaching(true); + ccdbmgr.setFatalWhenNull(false); + ccdbmgr.setURL("http://alice-ccdb.cern.ch"); + auto runDuration = ccdbmgr.getRunDuration(runNumber); + auto tRun = runDuration.first + (runDuration.second - runDuration.first) / 2; // time stamp for the middle of the run duration + ccdbmgr.setTimestamp(tRun); + + // CTP orbit reset time, does not change during the run + const auto orbitResetTimeNS = ccdbmgr.get>("CTP/Calib/OrbitReset"); + const int64_t orbitResetTimeMS = (*orbitResetTimeNS)[0] * 1e-3; + LOGP(info, "Orbit reset time in MS is {}", orbitResetTimeMS); + + // Geometry, material budget and B-field + auto geoAligned = ccdbmgr.get("GLO/Config/GeometryAligned"); + auto magField = ccdbmgr.get("GLO/Config/GRPMagField"); + const o2::base::MatLayerCylSet* matLut = o2::base::MatLayerCylSet::rectifyPtrFromFile(ccdbmgr.get("GLO/Param/MatLUT")); + o2::base::Propagator::initFieldFromGRP(magField); + auto prop = o2::base::Propagator::Instance(); + prop->setMatLUT(matLut); + float magfieldvalue = static_cast(magField->getNominalL3Field()); + LOGP(info, "Nominal L3 field: {:.3f}", magfieldvalue); + + // GRP LHC and beam type + auto grplhc = ccdbmgr.get("GLO/Config/GRPLHCIF"); + const auto beamA = grplhc->getBeamZ(o2::constants::lhc::BeamA); + const auto beamC = grplhc->getBeamZ(o2::constants::lhc::BeamC); + const auto eCM = grplhc->getSqrtS(); + bool isPbPb = (beamA == 82 && beamC == 82); + LOGP(info, "BeamA: {}, BeamC: {}, isPbPb: {}, Ecm: {}", beamA, beamC, isPbPb, eCM); + + // Input + auto fileList = getInputFileList(fileInput); + + // Single-threaded when reading from AliEn: TGrid/xrootd access is not thread-safe. This overrides the + // nThreads argument. Detected from the actual input file entries rather than from fileInput itself, + // which for a GRID job is a local list of alien:// entries, so that local/Lustre input still gets the + // requested parallelism. + if (!fileList.empty() && fileList[0].rfind("alien://", 0) == 0) { + LOGP(info, "AliEn input detected (alien:// prefix) -- TGrid access is not thread-safe, forcing nThreads=1"); + nThreads = 1; + } + int nFileThreads = nThreads; + LOGP(info, "Using {} threads for processing", nFileThreads); + + // Set up binning + const auto z2xBins = o2::RangeTokenizer::tokenize(z2xBinning); + const auto y2xBins = o2::RangeTokenizer::tokenize(y2xBinning); + + TrackResiduals trackResiduals; + + trackResiduals.setZ2XBinning(z2xBins); + trackResiduals.setY2XBinning(y2xBins); + trackResiduals.init(); + + const int nY2XBins = trackResiduals.getNY2XBins(); + const int nZ2XBins = trackResiduals.getNZ2XBins(); + LOGP(info, "nY2XBins: {}, nZ2XBins: {}", nY2XBins, nZ2XBins); + + const float maxDistIntCls = 25.0; + + std::vector>>>> vec_residualsAll; // sector, voxX, voxF, voxZ, xyz + std::vector>>> vec_residuals_counterAll; // sector, voxX, voxF, voxZ + + vec_residualsAll.resize(NSectors); + vec_residuals_counterAll.resize(NSectors); + for (int isec = 0; isec < NSectors; isec++) { + vec_residualsAll[isec].resize(NRows); + vec_residuals_counterAll[isec].resize(NRows); + for (int ix = 0; ix < NRows; ix++) { + vec_residualsAll[isec][ix].resize(nY2XBins); + vec_residuals_counterAll[isec][ix].resize(nY2XBins); + for (int iy = 0; iy < nY2XBins; iy++) { + vec_residualsAll[isec][ix][iy].resize(nZ2XBins); + vec_residuals_counterAll[isec][ix][iy].resize(nZ2XBins); + for (int iz = 0; iz < nZ2XBins; iz++) { + vec_residualsAll[isec][ix][iy][iz].resize(3); + for (int ixyz = 0; ixyz < 3; ixyz++) { + vec_residualsAll[isec][ix][iy][iz][ixyz] = 0.0; + } + vec_residuals_counterAll[isec][ix][iy][iz] = 0; + } + } + } + } + + const int nSlices = NSectors * trackResiduals.getNY2XBins() * trackResiduals.getNZ2XBins(); + const Long64_t maxTracks = maxTracksPerSlice * nSlices; + const int minTracks = minTracksPerSlice * nSlices; + std::atomic nTracksProcessed{0}; + Long64_t nTracksProcessed_final{0}; + + // Do we have a correction map available that we should apply to the clusters before the map extraction? + std::array, NSectors> voxelResults{}; + if (voxMapInput.size()) { + LOGP(info, "[InputCorrMap]: A correction map has been provided. Will apply the corrections to the cluster residuals"); + LOGP(info, "[InputCorrMap]: Resizing voxelResults to number of voxels"); + for (int iSec = 0; iSec < NSectors; ++iSec) { + voxelResults[iSec].resize(trackResiduals.getNVoxelsPerSector()); + } + TrackResiduals::VoxRes* voxResPtr = nullptr; + std::unique_ptr fIn = std::make_unique(voxMapInput.c_str()); + if (!fIn->IsOpen() || fIn->IsZombie()) { + LOGP(fatal, "[InputCorrMap]: Could not open input file {}", voxMapInput); + } + LOGP(info, "[InputCorrMap]: Getting TTree of voxels"); + std::unique_ptr treeIn; + treeIn.reset((TTree*)fIn->Get("voxResTree")); + if (!treeIn) { + LOGP(fatal, "[InputCorrMap]: Could not extract voxResTree from input file {}", voxMapInput); + } + treeIn->SetBranchAddress("voxRes", &voxResPtr); + LOGP(info, "[InputCorrMap]: Getting voxel results and filling voxelResults"); + for (int iEntry = 0; iEntry < treeIn->GetEntries(); ++iEntry) { + treeIn->GetEntry(iEntry); + auto& voxRes = *voxResPtr; + voxelResults[voxRes.bsec][trackResiduals.getGlbVoxBin(voxRes.bvox)] = voxRes; + } + } + // voxelResults is only read inside doFileProcessing and is not touched between thread start and join, + // so it is shared by const reference instead of copied once per thread (copying would cost + // nFileThreads x 36 x nVoxPerSector VoxRes objects). + LOGP(info, "Sharing the provided input Map with all threads"); + + // Check for bad ranges + std::vector badRanges; + std::vector> badRanges_thread(nFileThreads); + bool invertBadRange = false; + if (badRangeList.length() > 0) { + if (badRangeList[0] == '-') { + LOGP(info, "Inverting badRange list!"); + invertBadRange = true; + badRangeList.erase(0, 1); + } + badRanges = loadRunTimeSpans(badRangeList, runNumber, badRangeSelection); + } + for (int iThread = 0; iThread < nFileThreads; ++iThread) { + badRanges_thread[iThread] = badRanges; + } + + // ---| Lumi estimators |--- + size_t lumiEntriesCTP = 0; + double lumiSumCTP = 0; + + // vector of selected values + std::vector orbitsSel; + // This macro does not read IDC scalers from CCDB (see the "IDC values" note above the OrbitLumiInfo + // tree below). These two stay empty and are written out only to keep the output format stable for + // readers that expect the branches; the values are filled in offline from timeMSsel. + std::vector idcScalerASel; + std::vector idcScalerCSel; + std::vector ctpLumiSel; + std::vector timeMSsel; // time in ms collected over all selected TFs + + //---------------------------- + const std::filesystem::path pFileOutput(fileOutput); + std::string outPath(pFileOutput.parent_path().c_str()); + if (outPath.empty()) { + outPath = "."; + } + + fair::Logger::SetConsoleSeverity(fair::Severity::error); + /////////////////////////// + // Create thread vectors // + /////////////////////////// + // trackResiduals (declared earlier, already configured) is shared read-only across threads -- no per-thread copy needed + + long int nEdgeClustersSkipped{0}; + std::vector nEdgeClustersSkipped_thread(nFileThreads, 0); // Does NEED to be merged + long int nTracksSkippedByBadRangeList{0}; + std::vector nTracksSkippedByBadRangeList_thread(nFileThreads, 0); // Does NEED to be merged + long int nTFs{0}; + long int nTFsSkippedByBadRangeList{0}; + long int nTFsSkippedByTimeWindow{0}; + std::vector nTFs_thread(nFileThreads, 0); // Does NEED to be merged + std::vector nTFsSkippedByBadRangeList_thread(nFileThreads, 0); // Does NEED to be merged + std::vector nTFsSkippedByTimeWindow_thread(nFileThreads, 0); // Does NEED to be merged + + std::vector voxels(NSectors * NRows * nY2XBins * nZ2XBins); // flat [sec][ix][iy][iz] -- shared circle pool, ONE instance for all threads (guarded by per-voxel mutex); sized directly since VoxelData (holds a mutex) cannot be resize()'d after construction // Does NOT need to be merged + + // residualsAll/NP/PP/NN + their counters now live directly in VoxelData (shared, per-voxel mutex), so no per-thread copies or later merge pass are needed for them. + + // The per-file input handles (TFile, TTreeReaders, TTreeReaderValues) and the I/O-rate sampling state + // are plain locals inside doFileProcessing: each thread only ever used its own slot, so they never + // needed to be shared vectors here. Only totalBytesReadPerf is still per-thread, because it is summed + // across threads after the join below. + std::vector totalBytesReadPerf_thread(nFileThreads, 0); // Does NEED to be merged + + std::vector lumiEntriesCTP_thread(nFileThreads, 0); // Does NEED to be merged + std::vector lumiSumCTP_thread(nFileThreads, 0); // Does NEED to be merged + + // vector of selected values + std::vector> orbitsSel_thread(nFileThreads); // Does NEED to be merged + std::vector> ctpLumiSel_thread(nFileThreads); // Does NEED to be merged + std::vector> timeMSsel_thread(nFileThreads); // time in ms collected over all selected TFs // Does NEED to be merged + + fair::Logger::SetConsoleSeverity(fair::Severity::info); + printMemoryUsage("Memory usage after init buffers"); + + // Start threads + std::vector threads(nFileThreads); + for (int i = 0; i < nFileThreads; i++) { + threads[i] = std::thread(doFileProcessing, + i, + nFileThreads, + maxTrackWorkers, + firstTFTime, + lastTFTime, + invertBadRange, + maxdEdx, + maxdEdxExp, + maxDevdEdxOverExp, + skipEdgePads, + std::ref(nEdgeClustersSkipped_thread), + std::ref(nTracksSkippedByBadRangeList_thread), + std::ref(nTFs_thread), + std::ref(nTFsSkippedByBadRangeList_thread), + std::ref(nTFsSkippedByTimeWindow_thread), + voxMapInput, + sources, + orbitResetTimeMS, + magfieldvalue, + fileList, + maxTracksPerSlice, + maxTracks, + std::ref(nTracksProcessed), + std::cref(voxelResults), + std::ref(badRanges_thread), + std::cref(trackResiduals), + maxDistIntCls, + nY2XBins, + nZ2XBins, + std::ref(voxels), + std::ref(totalBytesReadPerf_thread), + std::ref(lumiEntriesCTP_thread), + std::ref(lumiSumCTP_thread), + std::ref(orbitsSel_thread), + std::ref(ctpLumiSel_thread), + std::ref(timeMSsel_thread)); + } + + // Wait for the threads to finish + for (auto& th : threads) { + th.join(); + } + + ////////////////////////////////////////////////////////////////////// + // ===| CODE TO MERGE VECTORS |======================================= + ////////////////////////////////////////////////////////////////////// + // START OF MERGE + nTracksProcessed_final = nTracksProcessed.load(); + Long64_t totalBytesReadPerf = 0; // sum of TTreePerfStats bytes read across all threads/files (network volume) + for (int iThread = 0; iThread < nFileThreads; ++iThread) { + // Merge counters + totalBytesReadPerf += totalBytesReadPerf_thread[iThread]; + lumiEntriesCTP += lumiEntriesCTP_thread[iThread]; + lumiSumCTP += lumiSumCTP_thread[iThread]; + nEdgeClustersSkipped += nEdgeClustersSkipped_thread[iThread]; + nTracksSkippedByBadRangeList += nTracksSkippedByBadRangeList_thread[iThread]; + nTFs += nTFs_thread[iThread]; + nTFsSkippedByBadRangeList += nTFsSkippedByBadRangeList_thread[iThread]; + nTFsSkippedByTimeWindow += nTFsSkippedByTimeWindow_thread[iThread]; + + // Merge vector data + orbitsSel.insert(orbitsSel.end(), + orbitsSel_thread[iThread].begin(), + orbitsSel_thread[iThread].end()); + + ctpLumiSel.insert(ctpLumiSel.end(), + ctpLumiSel_thread[iThread].begin(), + ctpLumiSel_thread[iThread].end()); + + timeMSsel.insert(timeMSsel.end(), + timeMSsel_thread[iThread].begin(), + timeMSsel_thread[iThread].end()); + } + + { + const double totalMBReadPerf = totalBytesReadPerf / (1024.0 * 1024.0); + LOGP(info, "All threads done | read {:.1f} MB total (unbinnedResid, across {} thread(s))", totalMBReadPerf, nFileThreads); + } + + // Compute final binned residuals directly from the shared per-voxel accumulators in `voxels` + // (each thread already accumulated straight into the single shared VoxelData under vox.mtx, + // so there is no per-thread data left to sum over here). + for (int isec = 0; isec < NSectors; isec++) { + for (int iz = 0; iz < nZ2XBins; iz++) { + for (int iy = 0; iy < nY2XBins; iy++) { + for (int ix = 0; ix < NRows; ix++) { + const auto& vox = voxels[((isec * NRows + ix) * nY2XBins + iy) * nZ2XBins + iz]; + + // weights as in original code + double weightNP = vox.counterNP * 1.0; + double weightPP = vox.counterPP * 0.01; + double weightNN = vox.counterNN * 0.01; + double sum_weight = weightNP + weightPP + weightNN; + + // For ixyz == 0,1: weighted average as before + for (int ixyz = 0; ixyz < 2; ++ixyz) { + if (sum_weight > 0.0) { + double meanNP = (vox.counterNP > 0) ? (vox.residualsNP[ixyz] / vox.counterNP) : 0.0; + double meanPP = (vox.counterPP > 0) ? (vox.residualsPP[ixyz] / vox.counterPP) : 0.0; + double meanNN = (vox.counterNN > 0) ? (vox.residualsNN[ixyz] / vox.counterNN) : 0.0; + vec_residualsAll[isec][ix][iy][iz][ixyz] = static_cast((weightNP * meanNP + weightPP * meanPP + weightNN * meanNN) / sum_weight); + } else { + vec_residualsAll[isec][ix][iy][iz][ixyz] = 0.0f; + } + } + // For ixyz == 2: simple mean; vox.residualsAll[2] is the running sum of DZdist, vox.counterZAll the count + vec_residualsAll[isec][ix][iy][iz][2] = (vox.counterZAll > 0) ? static_cast(vox.residualsAll[2] / vox.counterZAll) : 0.0f; + vec_residuals_counterAll[isec][ix][iy][iz] = vox.counterNP + vox.counterPP + vox.counterNN; + } + } + } + } + // END OF MERGE + ////////////////////////////////////////////////////////////////////// + + bool isBadCalib = false; + if ((minTracksPerSlice > 0) && (nTracksProcessed_final < minTracks)) { + LOGP(error, "Processed tracks: {} ({}), max requested tracks: {} ({}), minimum number of tracks not reached {} ({}), calibration will be marked as bad, skipped {} edge clusters, skipped tracks by badRangeList {} ({}), skipped TFs outside requested time window {} of {} ({:.1f}%)", nTracksProcessed_final, nTracksProcessed_final / nSlices, maxTracks, maxTracksPerSlice, minTracks, minTracksPerSlice, nEdgeClustersSkipped, nTracksSkippedByBadRangeList, nTracksSkippedByBadRangeList / nSlices, nTFsSkippedByTimeWindow, nTFs, (nTFs > 0) ? (100.0 * nTFsSkippedByTimeWindow / nTFs) : 0.0); + LOGP(info, "Processed time: {}ms, skipped time by badRangeList {}ms ({})", nTFs * o2::constants::lhc::LHCOrbitMUS * 1.e-3 * 32, nTFsSkippedByBadRangeList * o2::constants::lhc::LHCOrbitMUS * 1.e-3 * 32, float(nTFsSkippedByBadRangeList) / float(nTFs)); + const std::string stem = fs::path(fileOutput.data()).stem().c_str(); + std::ofstream(fmt::format("badCalib.{}", stem)).close(); + isBadCalib = true; + } else { + LOGP(info, "Processed tracks: {} ({}), max requested tracks: {} ({}), skipped {} edge clusters, skipped tracks by badRangeList {} ({}), skipped TFs outside requested time window {} of {} ({:.1f}%)", nTracksProcessed_final, nTracksProcessed_final / nSlices, maxTracks, maxTracksPerSlice, nEdgeClustersSkipped, nTracksSkippedByBadRangeList, nTracksSkippedByBadRangeList / nSlices, nTFsSkippedByTimeWindow, nTFs, (nTFs > 0) ? (100.0 * nTFsSkippedByTimeWindow / nTFs) : 0.0); + LOGP(info, "Processed time: {}ms, skipped time by badRangeList {}ms ({})", nTFs * o2::constants::lhc::LHCOrbitMUS * 1.e-3 * 32, nTFsSkippedByBadRangeList * o2::constants::lhc::LHCOrbitMUS * 1.e-3 * 32, float(nTFsSkippedByBadRangeList) / float(nTFs)); + } + //---------------------------------------------------------------- + + // IDC values: this macro deliberately does not query the TPC scalers from CCDB. Doing so per TF from + // inside the processing loop is slow and unreliable (uncached fetch, object reload on every jump in + // time), so meanIDC/medianIDC are written as 0 placeholders here. The per-TF timestamps needed to + // recover them are written to the OrbitLumiInfo tree below (timeMSsel), so the real values can be + // joined in afterwards, offline, against a properly cached CCDB client. + float meanIDC = 0.f; // not const: written to a TTree branch below, which needs a mutable address + + double meanCTP = 0; + if (lumiEntriesCTP > 0) { + meanCTP = lumiSumCTP / lumiEntriesCTP * (isPbPb ? 2.414 : 1); // 2.414 for PbPb + } + + TFile* outputfile = new TFile(fileOutput.c_str(), "RECREATE"); + LOGP(info, "Output file: {} created", fileOutput); + + o2::tpc::TrackResiduals::VoxRes mVoxelResultsOut{}; ///< the results from mVoxelResults are copied in here to be able to stream them + o2::tpc::TrackResiduals::VoxRes* mVoxelResultsOutPtr{&mVoxelResultsOut}; ///< pointer to set the branch address to for the output + std::unique_ptr mTreeOut; + + // Same tree-alias set as the real TrackResiduals::createOutputFile() (SpacePoints/TrackResiduals.cxx), + // so this tree can be TTree::Draw()'n the same way downstream regardless of which of the two wrote it. + if (trackResiduals.getNVoxelsPerSector() == 0) { + LOGP(warning, "For the tree aliases to work you must initialize the binning before calling createOutputFile()"); + } + mTreeOut = std::make_unique("voxResTree", "voxRes map results and statistics"); + mTreeOut->SetAlias("z2xBin", "bvox[0]"); + mTreeOut->SetAlias("y2xBin", "bvox[1]"); + mTreeOut->SetAlias("xBin", "bvox[2]"); + mTreeOut->SetAlias("z2xAV", "stat[0]"); + mTreeOut->SetAlias("y2xAV", "stat[1]"); + mTreeOut->SetAlias("xAV", "stat[2]"); + mTreeOut->SetAlias("fsector", "bsec+0.5+9.*(y2xAV)/pi"); + mTreeOut->SetAlias("phi", "(bsec%18+0.5+9.*(stat[1])/pi)/9*pi"); + mTreeOut->SetAlias("r", "stat[2]"); + mTreeOut->SetAlias("z", "z2xAV*xAV"); + mTreeOut->SetAlias("dX", "D[0]"); + mTreeOut->SetAlias("dY", "D[1]"); + mTreeOut->SetAlias("dZ", "D[2]"); + mTreeOut->SetAlias("dXS", "DS[0]"); + mTreeOut->SetAlias("dYS", "DS[1]"); + mTreeOut->SetAlias("dZS", "DS[2]"); + mTreeOut->SetAlias("dXE", "E[0]"); + mTreeOut->SetAlias("dYE", "E[1]"); + mTreeOut->SetAlias("dZE", "E[2]"); + mTreeOut->SetAlias("voxelIndex", Form("xBin + %i * (y2xBin + %i * z2xBin) + %i * bsec", trackResiduals.getNXBins(), trackResiduals.getNY2XBins(), trackResiduals.getNVoxelsPerSector())); + mTreeOut->SetAlias("entries", "stat[3]"); + mTreeOut->SetAlias("fitOK", Form("(flags & %u) == %u", TrackResiduals::DistDone, TrackResiduals::DistDone)); + mTreeOut->SetAlias("dispOK", Form("(flags & %u) == %u", TrackResiduals::DispDone, TrackResiduals::DispDone)); + mTreeOut->SetAlias("smtOK", Form("(flags & %u) == %u", TrackResiduals::SmoothDone, TrackResiduals::SmoothDone)); + mTreeOut->SetAlias("masked", Form("(flags & %u) == %u", TrackResiduals::Masked, TrackResiduals::Masked)); + mTreeOut->Branch("voxRes", &mVoxelResultsOutPtr); + + // Placeholder, filled in offline together with meanIDC -- see the note above. + float medianIDC = 0.f; + float medianCTP = static_cast(calculateMedian(ctpLumiSel)); + long medianTimeMS = static_cast(calculateMedian(timeMSsel)); + long meanTimeMS = static_cast(calculateMean(timeMSsel)); + + auto userInfo = mTreeOut->GetUserInfo(); + userInfo->Add(new TNamed("meanIDC", std::to_string(meanIDC).data())); + userInfo->Add(new TNamed("meanCTP", std::to_string(meanCTP).data())); + userInfo->Add(new TNamed("meanTimeMS", std::to_string(meanTimeMS).data())); + userInfo->Add(new TNamed("medianIDC", std::to_string(medianIDC).data())); + userInfo->Add(new TNamed("medianCTP", std::to_string(medianCTP).data())); + userInfo->Add(new TNamed("medianTimeMS", std::to_string(medianTimeMS).data())); + userInfo->Add(new TNamed("y2xBinning", y2xBinning.data())); + userInfo->Add(new TNamed("z2xBinning", z2xBinning.data())); + // TrackResiduals::setZ2XBinning() scales the physical z/x bin boundaries by scdcalib.maxZ2X -- this + // value is baked into what each z2x voxel index means in THIS tree, not just a cosmetic config knob. + // Stage 2 has no scdconfig.ini on the GRID and would otherwise silently reconstruct the binning with + // the O2 code default (1.0) instead of maxZ2XCut (1.4 in production), misaligning its geometry against + // the one this tree's voxels were actually filled with. Stored here so stage 2 can apply the exact + // same value it was built with, not a separately-configured guess. + userInfo->Add(new TNamed("maxZ2X", std::to_string(maxZ2XCut).data())); + userInfo->Add(new TNamed("nSlicesPhiZ", std::to_string(nSlices))); + userInfo->Add(new TNamed("maxTracks", std::to_string(maxTracks))); + userInfo->Add(new TNamed("minTracks", std::to_string(minTracks))); + userInfo->Add(new TNamed("nTracksProcessed", std::to_string(nTracksProcessed_final))); + if (isBadCalib) { + userInfo->Add(new TNamed("badCalib", "1")); + } + + for (int isec = 0; isec < NSectors; isec++) { + for (int iz = 0; iz < nZ2XBins; iz++) { + for (int iy = 0; iy < nY2XBins; iy++) { + for (int ix = 0; ix < NRows; ix++) { + for (int ixyz = 0; ixyz < 3; ixyz++) { + mVoxelResultsOut.D[ixyz] = vec_residualsAll[isec][ix][iy][iz][ixyz]; + mVoxelResultsOut.DS[ixyz] = vec_residualsAll[isec][ix][iy][iz][ixyz]; + mVoxelResultsOut.DC[ixyz] = vec_residualsAll[isec][ix][iy][iz][ixyz]; + mVoxelResultsOut.E[ixyz] = 0.1; + } + + float xposvox, yoverxpos, zoverxpos; + trackResiduals.getVoxelCoordinates(isec, ix, iy, iz, xposvox, yoverxpos, zoverxpos); + + mVoxelResultsOut.stat[0] = static_cast(zoverxpos); // z/x, y/x, x, entries + mVoxelResultsOut.stat[1] = static_cast(yoverxpos); + mVoxelResultsOut.stat[2] = static_cast(xposvox); + mVoxelResultsOut.stat[3] = static_cast(vec_residuals_counterAll[isec][ix][iy][iz]); // number of entries used + + mVoxelResultsOut.EXYCorr = 1.0; + mVoxelResultsOut.dYSigMAD = 1.0; + mVoxelResultsOut.dZSigLTM = 1.0; + + mVoxelResultsOut.bvox[0] = iz; + mVoxelResultsOut.bvox[1] = iy; + mVoxelResultsOut.bvox[2] = ix; + mVoxelResultsOut.bsec = isec; + mVoxelResultsOut.flags = 7; + + mTreeOut->Fill(); + } + } + } + } + + // write orbit and lumi info + outputfile->cd(); + TTree tOrbitLumi("OrbitLumiInfo", "Orbit and Lumi Info"); + int64_t orbitResetMS = orbitResetTimeMS; + tOrbitLumi.Branch("orbitResetTimeMS", &orbitResetMS); + tOrbitLumi.Branch("timeMSsel", &timeMSsel); + tOrbitLumi.Branch("orbitsSel", &orbitsSel); + tOrbitLumi.Branch("idcScalerASel", &idcScalerASel); + tOrbitLumi.Branch("idcScalerCSel", &idcScalerCSel); + tOrbitLumi.Branch("ctpLumiSel", &ctpLumiSel); + tOrbitLumi.Branch("meanIDC", &meanIDC); + tOrbitLumi.Branch("meanCTP", &meanCTP); + tOrbitLumi.Branch("meanTimeMS", &meanTimeMS); + tOrbitLumi.Branch("medianIDC", &medianIDC); + tOrbitLumi.Branch("medianCTP", &medianCTP); + tOrbitLumi.Branch("medianTimeMS", &medianTimeMS); + tOrbitLumi.Fill(); + tOrbitLumi.Write(); + + { + TTree tMetaData("MetaData", "Meta data information"); + + int nSlicesP = nSlices; + int maxTracksP = maxTracks; + int minTracksP = minTracks; + + tMetaData.Branch("runNumber", &runNumber); + tMetaData.Branch("nSlicesPhiZ", &nSlicesP); + tMetaData.Branch("maxTracks", &maxTracksP); + tMetaData.Branch("minTracks", &minTracksP); + tMetaData.Branch("nTracksProcessed", &nTracksProcessed_final); + tMetaData.Branch("fileOutput", &fileOutput); + tMetaData.Branch("voxMapInput", &voxMapInput); + tMetaData.Branch("trackSources", &trackSources); + tMetaData.Branch("z2xBinning", &z2xBinning); + tMetaData.Branch("y2xBinning", &y2xBinning); + tMetaData.Branch("useSmoothed", &useSmoothed); + tMetaData.Branch("createSpline", &createSpline); + tMetaData.Branch("maxTracksPerSlice", &maxTracksPerSlice); + tMetaData.Branch("minTracksPerSlice", &minTracksPerSlice); + tMetaData.Branch("badRangeList", &badRangeList); + tMetaData.Branch("firstTFTime", &firstTFTime); + tMetaData.Branch("lastTFTime", &lastTFTime); + tMetaData.Fill(); + tMetaData.Write(); + } + + outputfile->Write(); + //---------------------------------------------------------------- + + const std::string fileOutputInfo = fmt::format("{}/{}.txt", outPath, pFileOutput.stem().c_str()); + std::ofstream fInfo(fileOutputInfo); + fInfo << "meanIDC: " << meanIDC << "\n"; + fInfo << "meanCTP: " << meanCTP << "\n"; + fInfo << "meanTimeMS: " << meanTimeMS << "\n"; + fInfo << "medianIDC: " << medianIDC << "\n"; + fInfo << "medianCTP: " << medianCTP << "\n"; + fInfo << "medianTimeMS: " << medianTimeMS << "\n"; + fInfo.close(); + LOGP(info, "Found meanIDC: {}", meanIDC); + LOGP(info, "Found meanCTP: {}", meanCTP); + LOGP(info, "Found meanTimeMS: {}", meanTimeMS); + LOGP(info, "Found medianIDC: {}", medianIDC); + LOGP(info, "Found medianCTP: {}", medianCTP); + LOGP(info, "Found medianTimeMS: {}", medianTimeMS); + + // This macro only produces the raw voxel-residual map. Turning that into a TPCFastTransform is a + // separate step, done afterwards by TPCFastTransformInitCPM.C on this output, so that the two can be + // rerun independently. The createSpline argument is kept because it is recorded in the output + // metadata and consumed by that later step. + + // The per-thread input handles are locals inside doFileProcessing and are already released, in + // dependency order, when each thread returns -- nothing to tear down here. + mTreeOut.reset(); + + const auto t_end_1 = std::chrono::high_resolution_clock::now(); + LOGP(info, "Wall-clock time for the whole application: {:.1f} s", + std::chrono::duration(t_end_1 - t_start).count()); + + LOGP(info, "Done processing"); + + printMemoryUsage("Memory usage at end"); +} + +std::vector loadRunTimeSpans(const std::string& flname, int onlyRun, const std::string& selection = "ALL") +{ + std::ifstream inputFile(flname); + if (!inputFile) { + LOGP(fatal, "Failed to open selected run/timespans file {}", flname); + } + LOGP(info, "Reading bad ranges from file {}, for run {}", flname, onlyRun); + auto& ccdbmgr = o2::ccdb::BasicCCDBManager::instance(); + ccdbmgr.setURL("http://alice-ccdb.cern.ch"); + + ccdbmgr.setCaching(true); + ccdbmgr.setFatalWhenNull(false); + + std::vector badRanges; + + std::string line; + size_t cntl = 0, cntr = 0; + int64_t orbitResetTimeMS = 0; + int lastRunOrbitReset = -1; + while (std::getline(inputFile, line)) { + cntl++; + for (char& ch : line) { // Replace semicolons and tabs with spaces for uniform processing + if (ch == ';' || ch == '\t' || ch == ',') { + ch = ' '; + } + } + o2::utils::Str::trim(line); + if (line.size() < 1 || line[0] == '#') { + continue; + } + auto tokens = o2::utils::Str::tokenize(line, ' '); + auto logError = [&cntl, &line]() { LOGP(error, "Expected format for selection is tripplet , failed on line#{}: {}", cntl, line); }; + if (tokens.size() >= 3) { + int run = 0; + long rmin, rmax; + try { + run = std::stoi(tokens[0]); + rmin = std::stol(tokens[1]); + rmax = std::stol(tokens[2]); + } catch (...) { + logError(); + continue; + } + + if (onlyRun != run) { + continue; + } + + if (selection != "ALL") { + bool isSelection = false; + for (int iToken = 3; iToken < int(tokens.size()); ++iToken) { + if (tokens[iToken] == selection) { + isSelection = true; + } + } + if (isSelection == false) { + continue; + } + } + + constexpr long ISTimeStamp = 1514761200000L; + int isTimeStampMin = rmin > ISTimeStamp ? 1 : 0, isTimeStampMax = rmax > ISTimeStamp ? 1 : 0; // values above ISTimeStamp are timestamps (need to be converted to orbits) + if (rmin > rmax) { + LOGP(fatal, "Provided range limits are not in increasing order, entry is {}", line); + } + if (isTimeStampMin != isTimeStampMax) { + LOGP(fatal, "Provided range limits should be both consistent either with orbit number or with unix timestamp in ms, entry is {}", line); + } + if (isTimeStampMin) { + if (lastRunOrbitReset != run) { + LOGP(info, "Input needs conversion from time stamps to orbit"); + const auto [sor, eor] = ccdbmgr.getRunDuration(run); + const long timeMeanRun = (sor + eor) / 2.; + const double lengthRun = (eor - sor); + const auto orbitResetTimeNS = ccdbmgr.getSpecific>("CTP/Calib/OrbitReset", timeMeanRun); + orbitResetTimeMS = (*orbitResetTimeNS)[0] * 1e-3; + + LOGP(info, "Run {}, sor {}, eor {}, duration {} (min)", run, sor, eor, lengthRun / 1000. / 60.); + LOGP(info, "Orbit reset time in MS is {}", orbitResetTimeMS); + lastRunOrbitReset = run; + } + const auto orbitToMS = o2::constants::lhc::LHCOrbitMUS * 1e-3; + const auto rMinIn = rmin, rMaxIn = rmax; + rmin = long((rmin - orbitResetTimeMS) / orbitToMS); + rmax = long(std::ceil((rmax - orbitResetTimeMS) / orbitToMS)); + LOGP(info, "Run {} input range [{} - {}] ms -> [{} - {}] orbits", run, rMinIn, rMaxIn, rmin, rmax); + } + + badRanges.emplace_back(rmin, rmax); + cntr++; + } else { + logError(); + } + } + return badRanges; +} diff --git a/Detectors/TPC/calibration/SpacePoints/macro/voxResQA.C b/Detectors/TPC/calibration/SpacePoints/macro/voxResQA.C new file mode 100644 index 0000000000000..dbc766b2571d4 --- /dev/null +++ b/Detectors/TPC/calibration/SpacePoints/macro/voxResQA.C @@ -0,0 +1,445 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include +#include +#include +#include +#include "TChain.h" +#include "TMath.h" +#include "TCanvas.h" +#include "THStack.h" +#include "TLegend.h" +#include "TProfile.h" +#include "TProfile2D.h" +#include "TObjArray.h" +#include "TStyle.h" +#include "TSystem.h" +#include "TLatex.h" +#include "TPaletteAxis.h" +#include "TPCBase/Utils.h" +#include "TPCBaseRecSim/Painter.h" +#include "CommonUtils/StringUtils.h" +#include "Framework/Logger.h" +#include "SpacePoints/TrackResiduals.h" + +std::string getString(TList* l, const std::string& name, std::string defaultVal = "0"); +int getNbins(const std::string& name, int defaultVal = 20, const char delim = ','); +void setStyle(TH1* hist, int idir); +void setSizes(TAxis* axis, float titleSize, float titleOffset, float labelSize); + +using namespace o2::tpc; +namespace fs = std::filesystem; + +void voxResQA(std::string inFilesCmd, std::string outFileNameAdd = "", bool drawErrors = false, bool useSmoothed = true, int z2xBinSel = -1) +{ + gStyle->SetOptStat(0); + + TObjArray arrCanvases1D; + arrCanvases1D.SetName("voxResQA"); + TObjArray arrCanvases2D; + arrCanvases2D.SetName("voxResQA"); + + auto hsdXvsRowA = new THStack; + auto hsdXvsRowC = new THStack; + auto hsdZvsRowA = new THStack; + auto hsdZvsRowC = new THStack; + + const std::string dXtitle = useSmoothed ? "dXS" : "dX"; + const std::string dYtitle = useSmoothed ? "dYS" : "dY"; + const std::string dZtitle = useSmoothed ? "dZS" : "dZ"; + + const std::string sFiles(gSystem->GetFromPipe(inFilesCmd.data())); + const auto arrFiles = o2::utils::Str::tokenize(sFiles, '\n'); + if (arrFiles.size() == 0) { + return; + } + int idir = 0; + for (const auto& inFile : arrFiles) { + const fs::path inPath(inFile); + const std::string fileTitle(std::string(inPath.stem()).substr(17)); + auto fileIDName = fileTitle; + std::replace(fileIDName.begin(), fileIDName.end(), '.', '_'); + std::replace(fileIDName.begin(), fileIDName.end(), '-', '_'); + TChain cVoxRes("voxResTree"); + cVoxRes.AddFile(inFile.data()); + + if (!cVoxRes.GetBranch("voxRes")) { + LOGP(error, "Could not find branch voxRes in file '{}'", inFile); + continue; + } + + o2::tpc::TrackResiduals::VoxRes* vox{nullptr}; + cVoxRes.SetBranchAddress("voxRes", &vox); + + // retrieve binning + // OBJ: TNamed meanIDC 0.295421 + // OBJ: TNamed meanCTP 118553.744497 + // OBJ: TNamed y2xBinning 20 + // OBJ: TNamed z2xBinning 20 + + cVoxRes.GetEntry(0); + const auto userInfo = cVoxRes.GetTree()->GetUserInfo(); + userInfo->Print(); + const auto y2xBinning = getString(userInfo, "y2xBinning"); + const auto z2xBinning = getString(userInfo, "z2xBinning"); + const auto meanIDC = std::stof(getString(userInfo, "meanIDC")); + const auto meanCTP = std::stof(getString(userInfo, "meanCTP")); + + const int nbinsY2X = getNbins(y2xBinning); + const int nBinsSector = nbinsY2X * 36; + const int nbinsZ2X = getNbins(z2xBinning); + + // ===| 1D histograms |===================================================== + TObjArray arrHists1D; + auto hEntriesDist = new TH1F(("hEntriesDist" + fileIDName).data(), (fileTitle + ";#entries").data(), 500, 0, 5000); + arrHists1D.Add(hEntriesDist); + auto hdXDist = new TH1F(("hdXDist" + fileIDName).data(), (fileTitle + ";" + dXtitle + " (cm)").data(), 100, -10, 20); + arrHists1D.Add(hdXDist); + auto hdYDist = new TH1F(("hdYDist" + fileIDName).data(), (fileTitle + ";" + dYtitle + " (cm)").data(), 100, -10, 10); + arrHists1D.Add(hdYDist); + auto hdZDist = new TH1F(("hdZDist" + fileIDName).data(), (fileTitle + ";" + dZtitle + " (cm)").data(), 100, -10, 10); + arrHists1D.Add(hdZDist); + auto hdYSigmaMAD = new TProfile(("hdYSigmaMAD" + fileIDName).data(), (fileTitle + ";sector;<#sigma_{MAD}(dY)> (cm)").data(), nBinsSector, 0, nBinsSector); + arrHists1D.Add(hdYSigmaMAD); + + auto hdXvsRowA = new TProfile(("hdXvsRowA" + fileIDName).data(), (fileTitle + " (A-Side);row;<" + dXtitle + "> (cm) z2xBin 0").data(), 152, 0, 152); + hsdXvsRowA->Add(hdXvsRowA); + hsdXvsRowA->SetTitle("(A-Side);row; (cm) z2xBin 0"); + setStyle(hdXvsRowA, idir); + + auto hdXvsRowC = new TProfile(("hdXvsRowC" + fileIDName).data(), (fileTitle + " (C-Side);row;<" + dXtitle + "> (cm) z2xBin 0").data(), 152, 0, 152); + hsdXvsRowC->Add(hdXvsRowC); + hsdXvsRowC->SetTitle("(C-Side);row; (cm) z2xBin 0"); + setStyle(hdXvsRowC, idir); + + auto hdZvsRowA = new TProfile(("hdZvsRowA" + fileIDName).data(), (fileTitle + " (A-Side);row;<" + dZtitle + "> (cm) z2xBin 0").data(), 152, 0, 152); + hsdZvsRowA->Add(hdZvsRowA); + hsdZvsRowA->SetTitle("(A-Side);row; (cm) z2xBin 0"); + setStyle(hdZvsRowA, idir); + + auto hdZvsRowC = new TProfile(("hdZvsRowC" + fileIDName).data(), (fileTitle + " (C-Side);row;<" + dZtitle + "> (cm) z2xBin 0").data(), 152, 0, 152); + hsdZvsRowC->Add(hdZvsRowC); + hsdZvsRowC->SetTitle(("(C-Side);row;" + dZtitle + " (cm) z2xBin 0").data()); + setStyle(hdZvsRowC, idir); + + auto hdZvsZ2X = new TProfile(("hdZvsZ2X" + fileIDName).data(), (fileTitle + ";z2x-bin;<" + dZtitle + "> (cm) row 80").data(), 2 * nbinsZ2X, -nbinsZ2X, nbinsZ2X); + arrHists1D.Add(hdZvsZ2X); + + // ===| 2D histograms |===================================================== + TObjArray arrHists2D; + auto hMeanEntries = new TProfile2D(("hMeanEntries_" + fileIDName).data(), (fileTitle + ";sector;row;").data(), nBinsSector, 0, nBinsSector, 152, 0, 152); + arrHists2D.Add(hMeanEntries); + auto hMeanEntrieszRow = new TProfile2D(("hMeanEntrieszRow" + fileIDName).data(), (fileTitle + ";z-bin;row; (cm)").data(), 2 * nbinsZ2X, -nbinsZ2X, nbinsZ2X, 152, 0, 152); + arrHists2D.Add(hMeanEntrieszRow); + auto hSigmaMADSecRow = new TProfile2D(("hSigmaMADSecRow" + fileIDName).data(), (fileTitle + ";sector;row;<#sigma_{MAD}(dY)> (cm)").data(), nBinsSector, 0, nBinsSector, 152, 0, 152); + arrHists2D.Add(hSigmaMADSecRow); + auto hdXSecRow = new TProfile2D(("hdXSecRow" + fileIDName).data(), (fileTitle + ";sector;row;<" + dXtitle + "> (cm)").data(), nBinsSector, 0, nBinsSector, 152, 0, 152); + arrHists2D.Add(hdXSecRow); + auto hdYSecRow = new TProfile2D(("hdYSecRow" + fileIDName).data(), (fileTitle + ";sector;row;<" + dYtitle + "> (cm)").data(), nBinsSector, 0, nBinsSector, 152, 0, 152); + arrHists2D.Add(hdYSecRow); + auto hdZzRow = new TProfile2D(("hdZzRow" + fileIDName).data(), (fileTitle + ";z2x-bin;row;<" + dZtitle + "> (cm)").data(), 2 * nbinsZ2X, -nbinsZ2X, nbinsZ2X, 152, 0, 152); + arrHists2D.Add(hdZzRow); + + TProfile2D* hdXESecRow = nullptr; + TProfile2D* hdYESecRow = nullptr; + TProfile2D* hdZEzRow = nullptr; + if (drawErrors) { + hdXESecRow = new TProfile2D(("hdXESecRow" + fileIDName).data(), (fileTitle + ";sector;row; (cm)").data(), nBinsSector, 0, nBinsSector, 152, 0, 152); + arrHists2D.Add(hdXESecRow); + hdYESecRow = new TProfile2D(("hdYESecRow" + fileIDName).data(), (fileTitle + ";sector;row; (cm)").data(), nBinsSector, 0, nBinsSector, 152, 0, 152); + arrHists2D.Add(hdYESecRow); + hdZEzRow = new TProfile2D(("hdZEzRow" + fileIDName).data(), (fileTitle + ";z-bin;row; (cm)").data(), 2 * nbinsZ2X, -nbinsZ2X, nbinsZ2X, 152, 0, 152); + arrHists2D.Add(hdZEzRow); + } + + /* + OBJ: TNamed z2xBin bvox[0] + OBJ: TNamed y2xBin bvox[1] + OBJ: TNamed xBin bvox[2] + OBJ: TNamed z2xAV stat[0] + OBJ: TNamed y2xAV stat[1] + OBJ: TNamed xAV stat[2] + OBJ: TNamed fsector bsec+0.5+9.*(y2xAV)/pi + OBJ: TNamed phi (bsec%18+0.5+9.*(stat[1])/pi)/9*pi + OBJ: TNamed r stat[2] + OBJ: TNamed z z2xAV*xAV + OBJ: TNamed dX D[0] + OBJ: TNamed dY D[1] + OBJ: TNamed dZ D[2] + OBJ: TNamed dXS DS[0] + OBJ: TNamed dYS DS[1] + OBJ: TNamed dZS DS[2] + OBJ: TNamed dXE E[0] + OBJ: TNamed dYE E[1] + OBJ: TNamed dZE E[2] + OBJ: TNamed voxelIndex xBin + 152 * (y2xBin + 20 * z2xBin) + 60800 * bsec + OBJ: TNamed entries stat[3] + OBJ: TNamed fitOK (flags & 1) == 1 + OBJ: TNamed dispOK (flags & 2) == 2 + OBJ: TNamed smtOK (flags & 4) == 4 + OBJ: TNamed masked (flags & 128) == 128 + */ + + float z2xAV = 0; + + int isNaNdX = 0; + int isNaNdY = 0; + int isNaNdZ = 0; + int isNaNdXS = 0; + int isNaNdYS = 0; + int isNaNdZS = 0; + + for (Long64_t iEntry = 0; iEntry < cVoxRes.GetEntries(); ++iEntry) { + cVoxRes.GetEntry(iEntry); + const auto y2xBin = vox->bvox[1]; + const auto z2xBin = vox->bvox[0]; + const auto xBin = vox->bvox[2]; + const auto bsec = vox->bsec; + const auto entries = vox->stat[3]; + const auto dX = vox->D[0]; + const auto dY = vox->D[1]; + const auto dZ = vox->D[2]; + const auto dXS = vox->DS[0]; + const auto dYS = vox->DS[1]; + const auto dZS = vox->DS[2]; + const auto dXE = vox->E[0]; + const auto dYE = vox->E[1]; + const auto dZE = vox->E[2]; + const auto sectorFine = y2xBin + bsec * nbinsY2X; + const auto z2xBinSides = (z2xBin + 0.5) * (1 - 2 * (bsec > 17)); + const auto z = vox->stat[0] * vox->stat[2]; + + const auto dXdraw = useSmoothed ? dXS : dX; + const auto dYdraw = useSmoothed ? dYS : dY; + const auto dZdraw = useSmoothed ? dZS : dZ; + + isNaNdX += TMath::IsNaN(dX); + isNaNdY += TMath::IsNaN(dY); + isNaNdZ += TMath::IsNaN(dZ); + isNaNdXS += TMath::IsNaN(dXS); + isNaNdYS += TMath::IsNaN(dYS); + isNaNdZS += TMath::IsNaN(dZS); + + // only fill values in the acceptance + if (std::abs(z) < 248) { + if (z2xBinSel < 0 || z2xBinSel == z2xBin) { + if (z2xAV == 0) { + z2xAV = vox->stat[0]; + } + hMeanEntries->Fill(sectorFine, xBin, entries); + hdXSecRow->Fill(sectorFine, xBin, dXdraw); + hdYSecRow->Fill(sectorFine, xBin, dYdraw); + hSigmaMADSecRow->Fill(sectorFine, xBin, vox->dYSigMAD); + hdYSigmaMAD->Fill(sectorFine, vox->dYSigMAD); + } + } + + hMeanEntrieszRow->Fill(z2xBinSides, xBin, entries); + hdZzRow->Fill(z2xBinSides, xBin, dZdraw); + if (drawErrors && (z2xBinSel < 0 || z2xBinSel == z2xBin)) { + hdXESecRow->Fill(sectorFine, xBin, dXE); + hdYESecRow->Fill(sectorFine, xBin, dYE); + hdZEzRow->Fill(z2xBinSides, xBin, dZE); + } + + hEntriesDist->Fill(entries); + hdXDist->Fill(dXdraw); + hdYDist->Fill(dYdraw); + hdZDist->Fill(dZdraw); + if (xBin >= 79 && xBin <= 81) { + hdZvsZ2X->Fill(z2xBinSides, dZdraw); + } + + if (z2xBin == 0) { + if (bsec < 18) { + hdXvsRowA->Fill(xBin, dXdraw); + hdZvsRowA->Fill(xBin, dZdraw); + } else { + hdXvsRowC->Fill(xBin, dXdraw); + hdZvsRowC->Fill(xBin, dZdraw); + } + } + } + + std::vector hXadjust{hMeanEntries, hdXSecRow, hdXESecRow, hdYSecRow, hdYESecRow}; + for (auto h : hXadjust) { + if (!h) { + continue; + } + h->GetXaxis()->SetLimits(0, 36); + // 36 exact divisions means a label for every single sector -- unreadable at this pad size, they + // overlap into an illegible block. 12 (label every 3 sectors) still shows the A-/C-side structure + // without the collision. + h->GetXaxis()->SetNdivisions(12, false); + } + + // hEntriesDist's fixed 0-5000 range is mostly empty for real data (per-voxel entry counts rarely get + // anywhere near 5000) -- zoom to just past the last populated bin instead of showing mostly blank + // axis. + if (hEntriesDist->GetEntries() > 0) { + const int lastBin = hEntriesDist->FindLastBinAbove(0); + if (lastBin > 0) { + hEntriesDist->GetXaxis()->SetRangeUser(0, hEntriesDist->GetXaxis()->GetBinUpEdge(lastBin) * 1.1); + } + } + + // ===| output canvases |=================================================== + // + auto c1D = new TCanvas(("c1D_" + fileIDName + outFileNameAdd).data(), fileTitle.data(), 1500, 900); + arrCanvases1D.Add(c1D); + + int ipad = 1; + c1D->DivideSquare(arrHists1D.GetEntries()); + + for (auto o : arrHists1D) { + c1D->cd(ipad++); + o->Draw(); + const std::string name(o->GetName()); + if (o->IsA() != TProfile::Class()) { + gPad->SetLogy(); + } + // Without this, saveCanvas()'s plain c.SaveAs() (Detectors/TPC/base/src/Utils.cxx) can export a + // pad before its log-scale range is actually recomputed in batch mode -- confirmed real: the + // stored TCanvas in voxResQA*_1D.root has all real data (reopening and redrawing it interactively + // shows every panel fine), but the direct PNG export came out blank. The c2D loop below already + // does this after its own Draw() calls; c1D's was missing it. + gPad->Modified(); + gPad->Update(); + } + + auto c2D = new TCanvas(("c2D_" + fileIDName + outFileNameAdd).data(), fileTitle.data(), 1500, 900); + arrCanvases2D.Add(c2D); + + ipad = 1; + c2D->DivideSquare(arrHists2D.GetEntries()); + + TLatex l; + l.SetTextFont(42); + // Without this, DrawLatex's (x,y) below are interpreted in the pad's USER (data-axis) coordinates, + // not normalized pad fractions -- 0.75/0.85 then lands almost at the frame's bottom-left corner + // (sector~0.75 of 36, row~0.85 of 152), overlapping the plotted content instead of sitting clear of + // it. SetNDC() makes the coordinates pad-fraction-based, and the y moved down (below the frame, + // rather than "0.85" which was never actually near the top). + l.SetNDC(); + + for (auto o : arrHists2D) { + auto h = static_cast(o); + c2D->cd(ipad++); + h->Draw("colz"); + const std::string name(h->GetName()); + if (name.find("hdZzRow") == 0) { + l.DrawLatex(0.4, 0.02, "y2xBin averaged"); + } else { + if (z2xBinSel >= 0) { + l.DrawLatex(0.4, 0.02, fmt::format("z2xBin = {} ({})", z2xBinSel, z2xAV).data()); + } else { + l.DrawLatex(0.4, 0.02, "z2xBin averaged"); + } + } + gPad->Modified(); + gPad->Update(); + + auto palette = (TPaletteAxis*)h->GetListOfFunctions()->FindObject("palette"); + if (palette) { + painter::adjustPalette(h, 0.92); + } + } + + ++idir; + int nNaN = isNaNdX + isNaNdY + isNaNdZ; + int nNaNS = isNaNdXS + isNaNdYS + isNaNdZS; + const auto sNaN = fmt::format("NaN: {} - {} {} {} {}", inFile, nNaN, isNaNdX, isNaNdY, isNaNdZ); + const auto sNaNS = fmt::format("NaNS: {} - {} {} {} {}", inFile, nNaNS, isNaNdXS, isNaNdYS, isNaNdZS); + if (nNaN > 0) { + LOGP(error, "{}", sNaN); + } else { + LOGP(info, "{}", sNaN); + } + if (nNaNS > 0) { + LOGP(error, "{}", sNaNS); + } else { + LOGP(info, "{}", sNaNS); + } + } + + // ===| dX/dX vs row |======================================================== + // + auto cdXZvsRow = new TCanvas(fmt::format("cdXZvsRow{}", outFileNameAdd).data(), "dX/dZ vs row", 1500, 900); + cdXZvsRow->SetRightMargin(0.01); + cdXZvsRow->SetBottomMargin(0.15); + cdXZvsRow->Divide(1, 4, -1, -1); + cdXZvsRow->cd(1); + gPad->SetGrid(); + hsdXvsRowA->Draw("nostack"); + setSizes(hsdXvsRowA->GetYaxis(), 0.1, 0.4, 0.08); + setSizes(hsdXvsRowA->GetXaxis(), 0.1, 0.4, 0.08); + auto leg = gPad->BuildLegend(0.5, 0.5, 0.9, 0.9); + leg->SetMargin(0.05); + cdXZvsRow->cd(2); + gPad->SetGrid(); + hsdXvsRowC->Draw("nostack"); + setSizes(hsdXvsRowC->GetYaxis(), 0.1, 0.4, 0.08); + setSizes(hsdXvsRowC->GetXaxis(), 0.1, 0.4, 0.08); + cdXZvsRow->cd(3); + gPad->SetGrid(); + hsdZvsRowA->Draw("nostack"); + setSizes(hsdZvsRowA->GetYaxis(), 0.1, 0.4, 0.08); + setSizes(hsdZvsRowA->GetXaxis(), 0.1, 0.4, 0.08); + cdXZvsRow->cd(4); + gPad->SetGrid(); + hsdZvsRowC->Draw("nostack"); + setSizes(hsdZvsRowC->GetYaxis(), 0.1, 0.4, 0.08); + setSizes(hsdZvsRowC->GetXaxis(), 0.1, 0.4, 0.08); + + arrCanvases1D.Add(cdXZvsRow); + + // ===| save canvases |======================================================= + // + o2::tpc::utils::saveCanvases(arrCanvases1D, "./", "png,png", fmt::format("voxResQA{}_1D.root", outFileNameAdd.data())); + o2::tpc::utils::saveCanvases(arrCanvases2D, "./", "png,png", fmt::format("voxResQA{}_2D.root", outFileNameAdd.data())); +} + +std::string getString(TList* l, const std::string& name, std::string defaultVal) +{ + if (!l || !l->FindObject(name.data())) { + return defaultVal; + } + return l->FindObject(name.data())->GetTitle(); +} + +int getNbins(const std::string& name, int defaultVal, const char delim) +{ + if (name.find(delim) != name.npos) { + return std::count(name.begin(), name.end(), delim) + 1; + } + return std::stoi(name); +} + +void setStyle(TH1* hist, int idir) +{ + const std::vector colors = {kRed + 2, kOrange + 1, kGreen + 2, kAzure + 10, kBlue + 2, kMagenta + 1}; + const std::vector markers{20, 24, 21, 25, 47, 46, 34, 28}; + const std::vector styles{kSolid, kDashed, kDotted, kDashDotted}; + + hist->SetMarkerSize(1); + hist->SetMarkerColor(colors[idir % colors.size()]); + hist->SetLineColor(colors[idir % colors.size()]); + hist->SetMarkerStyle(markers[idir % markers.size()]); + hist->SetLineStyle(styles[(idir / colors.size()) % styles.size()]); +} + +void setSizes(TAxis* axis, float titleSize, float titleOffset, float labelSize) +{ + axis->SetTitleSize(titleSize); + axis->SetTitleOffset(titleOffset); + axis->SetLabelSize(labelSize); +} diff --git a/Detectors/TPC/workflow/CMakeLists.txt b/Detectors/TPC/workflow/CMakeLists.txt index f64a223f683d8..44bcfd23ebbc2 100644 --- a/Detectors/TPC/workflow/CMakeLists.txt +++ b/Detectors/TPC/workflow/CMakeLists.txt @@ -231,7 +231,9 @@ o2_add_executable(merge-integrate-cluster-workflow o2_add_executable(time-series-workflow COMPONENT_NAME tpc SOURCES src/tpc-time-series.cxx - PUBLIC_LINK_LIBRARIES O2::TPCWorkflow) + PUBLIC_LINK_LIBRARIES O2::TPCWorkflow + O2::GlobalTrackingWorkflowReaders + O2::GlobalTrackingWorkflowHelpers) o2_add_executable(scaler-workflow COMPONENT_NAME tpc diff --git a/Detectors/TPC/workflow/src/CalibratordEdxSpec.cxx b/Detectors/TPC/workflow/src/CalibratordEdxSpec.cxx index dea1d85899675..fc4c38b51bcd9 100644 --- a/Detectors/TPC/workflow/src/CalibratordEdxSpec.cxx +++ b/Detectors/TPC/workflow/src/CalibratordEdxSpec.cxx @@ -226,7 +226,6 @@ DataProcessorSpec getCalibratordEdxSpec(const o2::base::Propagator::MatCorrType enableAskMatLUT, // askMatLUT o2::base::GRPGeomRequest::None, // geometry inputs, - true, true); return DataProcessorSpec{ "tpc-calibrator-dEdx", diff --git a/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx b/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx index dce9703a6a365..317db93985ce3 100644 --- a/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx +++ b/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx @@ -41,6 +41,9 @@ #include #include "DataFormatsTPC/PIDResponse.h" #include "DataFormatsITS/TrackITS.h" +#include "DataFormatsTRD/TrackTRD.h" +#include "DataFormatsTRD/Tracklet64.h" +#include "DataFormatsTRD/CalibratedTracklet.h" #include "TROOT.h" #include "ReconstructionDataFormats/MatchInfoTOF.h" #include "DataFormatsTOF/Cluster.h" @@ -63,6 +66,13 @@ namespace tpc class TPCTimeSeries : public Task { public: + /// D2: per-track TRD tracklet lookup data + struct TRDTrackletData { + uint8_t trdPattern = 0; + uint8_t nTRDTracklets = 0; + int trackletIndices[6] = {-1, -1, -1, -1, -1, -1}; + }; + /// \constructor TPCTimeSeries(std::shared_ptr req, const bool disableWriter, const o2::base::Propagator::MatCorrType matType, const bool enableUnbinnedWriter, const bool tpcOnly, std::shared_ptr dr) : mCCDBRequest(req), mDisableWriter(disableWriter), mMatType(matType), mUnbinnedWriter(enableUnbinnedWriter), mTPCOnly(tpcOnly), mDataRequest(dr) {}; @@ -303,6 +313,38 @@ class TPCTimeSeries : public Task // find nearest vertex of tracks which have no vertex assigned findNearesVertex(tracksTPC, vertices); + // D2: build TPC track index → TRD tracklet data map (for unbinned output) + // For each TPC track that has a TRD match, store the TrackTRD tracklet indices + std::unordered_map tpcToTRDMap; + auto trdTracklets = mTPCOnly ? gsl::span() : recoData.getTRDTracklets(); + auto trdCalibTracklets = mTPCOnly ? gsl::span() : recoData.getTRDCalibratedTracklets(); + if (mUnbinnedWriter && !mTPCOnly) { + // scan ITS-TPC-TRD tracks + auto itstpctrdTracks = recoData.getITSTPCTRDTracks(); + for (unsigned int ig = 0; ig < itstpctrdTracks.size(); ++ig) { + auto gid = GTrackID(ig, GTrackID::ITSTPCTRD); + auto refTPC = recoData.getTPCContributorGID(gid); + if (!refTPC.isIndexSet()) { + continue; + } + auto refTRD = recoData.getSingleDetectorRefs(gid)[GTrackID::TRD]; + if (!refTRD.isIndexSet()) { + continue; + } + const auto& trdTrack = recoData.getTrack(refTRD); + TRDTrackletData trdData; + for (int iLay = 0; iLay < 6; ++iLay) { + auto trkltId = trdTrack.getTrackletIndex(iLay); + if (trkltId >= 0) { + trdData.trdPattern |= (1 << iLay); + trdData.nTRDTracklets++; + trdData.trackletIndices[iLay] = trkltId; + } + } + tpcToTRDMap[refTPC] = trdData; + } + } + // getting cluster references for cluster bitmask if (mUnbinnedWriter) { mTPCTrackClIdx = pc.inputs().get>("trackTPCClRefs"); @@ -472,7 +514,7 @@ class TPCTimeSeries : public Task auto myThread = [&](int iThread) { for (size_t i = iThread; i < loopEnd; i += mNThreads) { if (acceptTrack(tracksTPC[i])) { - fillDCA(tracksTPC, tracksITSTPC, vertices, i, iThread, indicesITSTPC, tracksITS, idxTPCTrackToTOFCluster, tofClusters); + fillDCA(tracksTPC, tracksITSTPC, vertices, i, iThread, indicesITSTPC, tracksITS, idxTPCTrackToTOFCluster, tofClusters, tpcToTRDMap, trdTracklets, trdCalibTracklets); } } }; @@ -489,7 +531,7 @@ class TPCTimeSeries : public Task auto myThread = [&](int iThread) { for (size_t i = iThread; i < loopEnd; i += mNThreads) { if (acceptTrack(tracksTPC[i])) { - fillDCA(tracksTPC, tracksITSTPC, vertices, i, iThread, indicesITSTPC, tracksITS, idxTPCTrackToTOFCluster, tofClusters); + fillDCA(tracksTPC, tracksITSTPC, vertices, i, iThread, indicesITSTPC, tracksITS, idxTPCTrackToTOFCluster, tofClusters, tpcToTRDMap, trdTracklets, trdCalibTracklets); } } }; @@ -1133,7 +1175,7 @@ class TPCTimeSeries : public Task return isGoodTrack; } - void fillDCA(const gsl::span tracksTPC, const gsl::span tracksITSTPC, const gsl::span vertices, const int iTrk, const int iThread, const std::unordered_map>& indicesITSTPC, const gsl::span tracksITS, const std::vector>& idxTPCTrackToTOFCluster, const gsl::span tofClusters) + void fillDCA(const gsl::span tracksTPC, const gsl::span tracksITSTPC, const gsl::span vertices, const int iTrk, const int iThread, const std::unordered_map>& indicesITSTPC, const gsl::span tracksITS, const std::vector>& idxTPCTrackToTOFCluster, const gsl::span tofClusters, const std::unordered_map& tpcToTRDMap, const gsl::span trdTracklets, const gsl::span trdCalibTracklets) { const auto& trackFull = tracksTPC[iTrk]; const bool isGoodTrack = checkTrack(trackFull); @@ -1179,21 +1221,22 @@ class TPCTimeSeries : public Task return; } - const int tglBin = mTglBins * std::abs(trackTmp.getTgl()) / mMaxTgl + mPhiBins; - const int phiBin = mPhiBins * trackTmp.getPhi() / o2::constants::math::TwoPI; + // Saturate bin indices — edge bins act as overflow (Phase 0.2 fix) + const int tglBin = std::clamp(static_cast(mTglBins * std::abs(trackTmp.getTgl()) / mMaxTgl) + mPhiBins, + mPhiBins, mPhiBins + mTglBins - 1); + const int phiBin = std::clamp(static_cast(mPhiBins * trackTmp.getPhi() / o2::constants::math::TwoPI), + 0, mPhiBins - 1); const int offsQPtBin = mPhiBins + mTglBins; - const int qPtBin = offsQPtBin + mQPtBins * (trackTmp.getQ2Pt() + mMaxQPt) / (2 * mMaxQPt); + const int qPtBin = std::clamp(offsQPtBin + static_cast(mQPtBins * (trackTmp.getQ2Pt() + mMaxQPt) / (2 * mMaxQPt)), + offsQPtBin, offsQPtBin + mQPtBins - 1); const int localMult = mNTracksWindow[iTrk]; const int offsMult = offsQPtBin + mQPtBins; - const int multBin = offsMult + mMultBins * localMult / mMultMax; + const int multBin = std::clamp(offsMult + static_cast(mMultBins * localMult / mMultMax), + offsMult, offsMult + mMultBins - 1); const int nBins = getNBins(); - if ((phiBin < 0) || (phiBin > mPhiBins) || (tglBin < mPhiBins) || (tglBin > offsQPtBin) || (qPtBin < offsQPtBin) || (qPtBin > offsMult) || (multBin < offsMult) || (multBin > offsMult + mMultBins)) { - return; - } - float sigmaY2 = 0; float sigmaZ2 = 0; const int sector = o2::math_utils::angle2Sector(trackTmp.getPhiPos()); @@ -1335,12 +1378,17 @@ class TPCTimeSeries : public Task if (mUnbinnedWriter && mStreamer[iThread]) { const float factorPt = mSamplingFactor; bool writeData = true; + bool writeDataITSTPC = false; float weight = 0; + float weightITSTPC = 0; if (mSampleTsallis) { std::uniform_real_distribution<> distr(0., 1.); writeData = o2::math_utils::Tsallis::downsampleTsallisCharged(tracksTPC[iTrk].getPt(), factorPt, mSqrt, weight, distr(mGenerator[iThread])); + if (hasITSTPC) { + writeDataITSTPC = o2::math_utils::Tsallis::downsampleTsallisCharged(tracksITSTPC[idxITSTPC.front()].getPt(), factorPt, mSqrt, weightITSTPC, distr(mGenerator[iThread])); + } } - if (writeData || minBiasOk) { + if (writeData || writeDataITSTPC || minBiasOk) { auto clusterMask = makeClusterBitMask(trackFull); const auto& trkOrig = tracksTPC[iTrk]; const bool isNearestVtx = (idxITSTPC.back() == -1); // is nearest vertex in case no vertex was found @@ -1349,6 +1397,30 @@ class TPCTimeSeries : public Task const float chi2match_ITSTPC = hasITSTPC ? tracksITSTPC[idxITSTPC.front()].getChi2Match() : -1; const int nClITS = idxITSCheck ? tracksITS[idxITSTrack].getNClusters() : -1; const int chi2ITS = idxITSCheck ? tracksITS[idxITSTrack].getChi2() : -1; + // D1: ITS cluster sizes (4-bit per layer, mask bit 28 = kSharedClusters) + const uint32_t itsClusterSizes = idxITSCheck ? (static_cast(tracksITS[idxITSTrack].getClusterSizes()) & 0x0FFFFFFFu) : 0u; + const bool itsHasSharedClusters = idxITSCheck ? tracksITS[idxITSTrack].hasSharedClusters() : false; + const uint32_t itsPattern = idxITSCheck ? (tracksITS[idxITSTrack].getPattern() & 0x7Fu) : 0u; + + // D2: TRD tracklet data — native objects per layer + uint8_t trdPattern = 0; + uint8_t nTRDTracklets = 0; + std::vector trdTrackletVec(6); + std::vector trdCalibVec(6); + auto itTRD = tpcToTRDMap.find(iTrk); + if (itTRD != tpcToTRDMap.end()) { + const auto& trdData = itTRD->second; + trdPattern = trdData.trdPattern; + nTRDTracklets = trdData.nTRDTracklets; + for (int iLay = 0; iLay < 6; ++iLay) { + if (trdData.trackletIndices[iLay] >= 0) { + trdTrackletVec[iLay] = trdTracklets[trdData.trackletIndices[iLay]]; + if (trdData.trackletIndices[iLay] < static_cast(trdCalibTracklets.size())) { + trdCalibVec[iLay] = trdCalibTracklets[trdData.trackletIndices[iLay]]; + } + } + } + } int typeSide = 2; // A- and C-Side cluster if (trackFull.hasASideClustersOnly()) { typeSide = 0; @@ -1395,7 +1467,11 @@ class TPCTimeSeries : public Task } } } - const int triggerMask = 0x1 * minBiasOk + 0x2 * writeData; + // triggerMask bits: + // 0x1: flat minimum-bias stream + // 0x2: Tsallis stream sampled with TPC-only pT + // 0x4: Tsallis stream sampled with ITS-TPC combined pT + const int triggerMask = 0x1 * minBiasOk + 0x2 * writeData + 0x4 * writeDataITSTPC; float deltaP2ConstrVtx = -999; float deltaP3ConstrVtx = -999; @@ -1446,6 +1522,7 @@ class TPCTimeSeries : public Task << "factorMinBias=" << factorMinBias << "factorPt=" << factorPt << "weight=" << weight + << "weight_ITSTPC=" << weightITSTPC << "dcar_tpc_vertex=" << dcaTPCAtVertex << "dcar_tpc=" << dca[0] << "dcaz_tpc=" << dca[1] @@ -1478,6 +1555,14 @@ class TPCTimeSeries : public Task << "mX_ITS=" << mx_ITS << "nClITS=" << nClITS << "chi2ITS=" << chi2ITS + << "itsClusterSizes=" << itsClusterSizes + << "itsHasSharedClusters=" << itsHasSharedClusters + << "itsPattern=" << itsPattern + // D2: TRD tracklet data + << "trdPattern=" << trdPattern + << "nTRDTracklets=" << nTRDTracklets + << "trdTracklets=" << trdTrackletVec + << "trdCalibTracklets=" << trdCalibVec << "chi2match_ITSTPC=" << chi2match_ITSTPC << "PID=" << trkOrig.getPID().getID() // TPC cov at vertex (without vertex constrained) @@ -1670,6 +1755,7 @@ class TPCTimeSeries : public Task std::unordered_map nContributors_ITS; // ITS: vertex ID -> n contributors std::unordered_map nContributors_ITSTPC; // ITS-TPC (and ITS-TPC-TRD, ITS-TPC-TOF, ITS-TPC-TRD-TOF): vertex ID -> n contributors + std::unordered_map nContributors_TRD; // ITS-TPC-TRD (and ITS-TPC-TRD-TOF): vertex ID -> n TRD-matched PV contributors // loop over collisions if (!vertices.empty()) { @@ -1690,6 +1776,10 @@ class TPCTimeSeries : public Task if (refITSTPC.isIndexSet()) { indicesITSTPC_vtx[refITSTPC] = vID; ++nContributors_ITSTPC[vID]; + // count TRD-matched PV contributors + if (source == TrkSrc::ITSTPCTRD || source == TrkSrc::ITSTPCTRDTOF) { + ++nContributors_TRD[vID]; + } } else { ++nContributors_ITS[vID]; } @@ -1751,6 +1841,17 @@ class TPCTimeSeries : public Task mBufferDCA.vertexY_ITSTPC_RMS.front() = avgVtxITSTPC[1].getStdDev(); mBufferDCA.vertexZ_ITSTPC_RMS.front() = avgVtxITSTPC[2].getStdDev(); + // TRD matching fraction (summed over all vertices in this TF) + int sumITSTPCBased = 0; + int sumWithTRD = 0; + for (int ivtx = 0; ivtx < vertices.size(); ++ivtx) { + sumITSTPCBased += nContributors_ITSTPC[ivtx]; + sumWithTRD += nContributors_TRD[ivtx]; + } + mBufferDCA.nITSTPCBasedPVContributors.front() = sumITSTPCBased; + mBufferDCA.nITSTPCWithTRDPVContributors.front() = sumWithTRD; + mBufferDCA.fracTRD.front() = (sumITSTPCBased > 0) ? static_cast(sumWithTRD) / sumITSTPCBased : std::nanf(""); + // quantiles and truncated mean RobustAverage avg(vertices.size(), false); for (const auto& vtx : vertices) { @@ -1840,6 +1941,10 @@ o2::framework::DataProcessorSpec getTPCTimeSeriesSpec(const bool disableWriter, if (src[GTrackID::TPC]) { dataRequest->requestClusters(GTrackID::getSourcesMask("TPC"), useMC); } + // D2: request TRD tracklets for tracks with TRD contribution + if (srcTracks[GTrackID::ITSTPCTRD] || srcTracks[GTrackID::ITSTPCTRDTOF]) { + dataRequest->requestTRDTracklets(useMC); + } bool tpcOnly = srcTracks == GTrackID::getSourcesMask("TPC"); if (srcTracks.any() && !tpcOnly) { diff --git a/Detectors/TPC/workflow/src/tpc-time-series.cxx b/Detectors/TPC/workflow/src/tpc-time-series.cxx index 782e45fb04673..06c3094f679c3 100644 --- a/Detectors/TPC/workflow/src/tpc-time-series.cxx +++ b/Detectors/TPC/workflow/src/tpc-time-series.cxx @@ -14,12 +14,25 @@ #include "TPCWorkflow/TPCTimeSeriesSpec.h" #include "TPCWorkflow/TPCTimeSeriesWriterSpec.h" +#include "DetectorsCommonDataFormats/DetID.h" #include "CommonUtils/ConfigurableParam.h" #include "TPCReaderWorkflow/TPCSectorCompletionPolicy.h" +#include "DetectorsBase/DPLWorkflowUtils.h" +#include "GlobalTrackingWorkflowHelpers/InputHelper.h" +#include "DetectorsRaw/HBFUtilsInitializer.h" +#include "DataFormatsITSMFT/DPLAlpideParamInitializer.h" #include "Framework/ConfigParamSpec.h" #include "GPUDebugStreamer.h" using namespace o2::framework; +using GID = o2::dataformats::GlobalTrackID; +using DetID = o2::detectors::DetID; + +// ------------------------------------------------------------------ +void customize(std::vector& policies) +{ + o2::raw::HBFUtilsInitializer::addNewTimeSliceCallback(policies); +} void customize(std::vector& workflowOptions) { @@ -27,10 +40,12 @@ void customize(std::vector& workflowOptions) std::vector options{ ConfigParamSpec{"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings"}}, {"disable-root-output", VariantType::Bool, false, {"disable root-files output writers"}}, + {"disable-root-input", VariantType::Bool, false, {"disable root-files input reader"}}, {"enable-unbinned-root-output", VariantType::Bool, false, {"writing out unbinned track data"}}, - {"track-sources", VariantType::String, std::string{o2::dataformats::GlobalTrackID::ALL}, {"comma-separated list of sources to use"}}, + {"track-sources", VariantType::String, std::string{GID::ALL}, {"comma-separated list of sources to use"}}, {"material-type", VariantType::Int, 2, {"Type for the material budget during track propagation: 0=None, 1=Geo, 2=LUT"}}}; - + o2::itsmft::DPLAlpideParamInitializer::addITSConfigOption(options); + o2::raw::HBFUtilsInitializer::addConfigOption(options); std::swap(workflowOptions, options); } @@ -42,11 +57,28 @@ WorkflowSpec defineDataProcessing(ConfigContext const& config) o2::conf::ConfigurableParam::updateFromString(config.options().get("configKeyValues")); const bool disableWriter = config.options().get("disable-root-output"); const bool enableUnbinnedWriter = config.options().get("enable-unbinned-root-output"); - auto src = o2::dataformats::GlobalTrackID::getSourcesMask(config.options().get("track-sources")); + GID::mask_t allowedSources = GID::getSourcesMask("ITS,TPC,ITS-TPC,ITS-TPC-TRD,ITS-TPC-TOF,ITS-TPC-TRD-TOF,FT0"); + auto srcTrc = allowedSources & GID::getSourcesMask(config.options().get("track-sources")); + o2::dataformats::GlobalTrackID::mask_t srcCls = GID::getSourcesMask("TPC"); + if (GID::includesDet(DetID::ITS, srcTrc)) { + srcCls |= GID::getSourcesMask("ITS"); + } + if (GID::includesDet(DetID::TRD, srcTrc)) { + srcCls |= GID::getSourcesMask("TRD"); + } + if (GID::includesDet(DetID::TOF, srcTrc)) { + srcCls |= GID::getSourcesMask("TOF"); + } + auto materialType = static_cast(config.options().get("material-type")); - workflow.emplace_back(o2::tpc::getTPCTimeSeriesSpec(disableWriter, materialType, enableUnbinnedWriter, src)); + + o2::globaltracking::InputHelper::addInputSpecs(config, workflow, srcCls, srcTrc, srcTrc, false); + o2::globaltracking::InputHelper::addInputSpecsPVertex(config, workflow, false); // P-vertex is always needed + + workflow.emplace_back(o2::tpc::getTPCTimeSeriesSpec(disableWriter, materialType, enableUnbinnedWriter, srcTrc)); if (!disableWriter) { workflow.emplace_back(o2::tpc::getTPCTimeSeriesWriterSpec()); } + o2::raw::HBFUtilsInitializer hbfIni(config, workflow); return workflow; } diff --git a/Detectors/TRD/workflow/include/TRDWorkflow/TRDGlobalTrackingSpec.h b/Detectors/TRD/workflow/include/TRDWorkflow/TRDGlobalTrackingSpec.h index acb061a4bcc86..c3a5be6a45649 100644 --- a/Detectors/TRD/workflow/include/TRDWorkflow/TRDGlobalTrackingSpec.h +++ b/Detectors/TRD/workflow/include/TRDWorkflow/TRDGlobalTrackingSpec.h @@ -66,6 +66,7 @@ class TRDGlobalTracking : public o2::framework::Task private: void updateTimeDependentParams(o2::framework::ProcessingContext& pc); + void storeConfigs(o2::framework::ProcessingContext& pc); o2::gpu::GPUTRDTracker* mTracker{nullptr}; ///< TRD tracking engine o2::gpu::GPUReconstruction* mRec{nullptr}; ///< GPU reconstruction pointer, handles memory for the tracker diff --git a/Detectors/TRD/workflow/src/TRDGlobalTrackingSpec.cxx b/Detectors/TRD/workflow/src/TRDGlobalTrackingSpec.cxx index 4aecd0e3df054..e541482dbde2a 100644 --- a/Detectors/TRD/workflow/src/TRDGlobalTrackingSpec.cxx +++ b/Detectors/TRD/workflow/src/TRDGlobalTrackingSpec.cxx @@ -11,6 +11,8 @@ /// @file TRDGlobalTrackingSpec.cxx +#include +#include #include "TRDWorkflow/TRDGlobalTrackingSpec.h" #include "TRDBase/Geometry.h" #include "DetectorsCommonDataFormats/DetectorNameConf.h" @@ -48,6 +50,7 @@ #include "GPUO2InterfaceConfiguration.h" #include "GPUO2InterfaceUtils.h" #include "GPUSettings.h" +#include "GPUO2ConfigurableParam.h" #include "GPUDataTypesIO.h" #include "GPUTRDDef.h" #include "GPUTRDTrack.h" @@ -281,6 +284,7 @@ void TRDGlobalTracking::run(ProcessingContext& pc) o2::globaltracking::RecoContainer inputTracks; inputTracks.collectData(pc, *mDataRequest); updateTimeDependentParams(pc); + storeConfigs(pc); mChainTracking->ClearIOPointers(); mTPCClusterIdxStruct = &inputTracks.inputsTPCclusters->clusterIndex; @@ -564,15 +568,22 @@ void TRDGlobalTracking::run(ProcessingContext& pc) } } + mTimer.Stop(); +} + +void TRDGlobalTracking::storeConfigs(ProcessingContext& pc) +{ static bool first = true; if (first) { first = false; if (pc.services().get().inputTimesliceId == 0) { o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, "GPU_rec_trd"), "GPU_rec_trd"); + TMap md; + md.SetOwnerKeyValue(); + md.Add(new TObjString(o2::gpu::internal::GPUConfigurableParamGPUSettingsRecTRD::Instance().getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(o2::gpu::internal::GPUConfigurableParamGPUSettingsRecTRD::Instance().getName()).c_str())); + pc.outputs().snapshot(Output{"META", "TRDTRACKER", 0}, md); } } - - mTimer.Stop(); } bool TRDGlobalTracking::refitITSTPCTRDTrack(TrackTRD& trk, float timeTRD, o2::globaltracking::RecoContainer* recoCont) @@ -1019,6 +1030,8 @@ DataProcessorSpec getTRDGlobalTrackingSpec(bool useMC, GTrackID::mask_t src, boo } } + outputs.emplace_back("META", "TRDTRACKER", 0, Lifetime::Sporadic); + std::string processorName = o2::utils::Str::concat_string("trd-globaltracking", GTrackID::getSourcesNames(src)); std::regex reg("[,\\[\\]]+"); processorName = regex_replace(processorName, reg, "_"); diff --git a/Detectors/Upgrades/ALICE3/CMakeLists.txt b/Detectors/Upgrades/ALICE3/CMakeLists.txt index 334bb13064783..f587772a1885b 100644 --- a/Detectors/Upgrades/ALICE3/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/CMakeLists.txt @@ -10,11 +10,10 @@ # or submit itself to any jurisdiction. add_subdirectory(Passive) -add_subdirectory(TRK) +add_subdirectory(TRKFT3) add_subdirectory(GlobalReconstruction) add_subdirectory(ECal) add_subdirectory(FD3) -add_subdirectory(FT3) add_subdirectory(FCT) add_subdirectory(AOD) add_subdirectory(IOTOF) diff --git a/Detectors/Upgrades/ALICE3/FT3/base/src/GeometryTGeo.cxx b/Detectors/Upgrades/ALICE3/FT3/base/src/GeometryTGeo.cxx deleted file mode 100644 index 73b2bc9b94eb8..0000000000000 --- a/Detectors/Upgrades/ALICE3/FT3/base/src/GeometryTGeo.cxx +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. -// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. -// All rights not expressly granted are reserved. -// -// This software is distributed under the terms of the GNU General Public -// License v3 (GPL Version 3), copied verbatim in the file "COPYING". -// -// In applying this license CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -/// \file GeometryTGeo.cxx -/// \brief Implementation of the GeometryTGeo class -/// \author cvetan.cheshkov@cern.ch - 15/02/2007 -/// \author ruben.shahoyan@cern.ch - adapted to ITSupg 18/07/2012 -/// \author rafael.pezzi@cern.ch - adapted to ALICE 3 EndCaps 14/02/2021 - -// ATTENTION: In opposite to old AliITSgeomTGeo, all indices start from 0, not from 1!!! - -#include "FT3Base/GeometryTGeo.h" -#include "DetectorsBase/GeometryManager.h" -#include "MathUtils/Cartesian.h" - -#include // for LOG - -#include // for TGeoBBox -#include // for gGeoManager, TGeoManager -#include // for TGeoPNEntry, TGeoPhysicalNode -#include // for TGeoShape -#include // for Nint, ATan2, RadToDeg -#include // for TString, Form -#include "TClass.h" // for TClass -#include "TGeoMatrix.h" // for TGeoHMatrix -#include "TGeoNode.h" // for TGeoNode, TGeoNodeMatrix -#include "TGeoVolume.h" // for TGeoVolume -#include "TMathBase.h" // for Max -#include "TObjArray.h" // for TObjArray -#include "TObject.h" // for TObject - -#include // for isdigit -#include // for snprintf, NULL, printf -#include // for strstr, strlen - -using namespace TMath; -using namespace o2::ft3; -using namespace o2::detectors; - -ClassImp(o2::ft3::GeometryTGeo); - -std::unique_ptr GeometryTGeo::sInstance; - -std::string GeometryTGeo::sVolumeName = "FT3V"; ///< Mother volume name -std::string GeometryTGeo::sInnerVolumeName = "FT3Inner"; ///< Mother inner volume name -std::string GeometryTGeo::sLayerName = "FT3Layer"; ///< Layer name -std::string GeometryTGeo::sChipName = "FT3Chip"; ///< Chip name -std::string GeometryTGeo::sSensorName = "FT3Sensor"; ///< Sensor name -std::string GeometryTGeo::sPassiveName = "FT3Passive"; ///< Passive material name - -//__________________________________________________________________________ -GeometryTGeo::GeometryTGeo(bool build, int loadTrans) : o2::itsmft::GeometryTGeo(DetID::FT3) -{ - // default c-tor, if build is true, the structures will be filled and the transform matrices - // will be cached - if (sInstance) { - LOG(fatal) << "Invalid use of public constructor: o2::ft3::GeometryTGeo instance exists"; - // throw std::runtime_error("Invalid use of public constructor: o2::ft3::GeometryTGeo instance exists"); - } - - if (build) { - Build(loadTrans); - } -} - -//__________________________________________________________________________ -void GeometryTGeo::Build(int loadTrans) -{ - if (isBuilt()) { - LOG(warning) << "Already built"; - return; // already initialized - } - - if (!gGeoManager) { - // RSTODO: in future there will be a method to load matrices from the CDB - LOG(fatal) << "Geometry is not loaded"; - } - - fillMatrixCache(loadTrans); -} - -//__________________________________________________________________________ -const char* GeometryTGeo::composeSymNameLayer(Int_t d, Int_t lr) -{ - return Form("%s/%s%d", composeSymNameFT3(d), getFT3LayerPattern(), lr); -} - -//__________________________________________________________________________ -const char* GeometryTGeo::composeSymNameChip(Int_t d, Int_t lr) -{ - return Form("%s/%s%d", composeSymNameLayer(d, lr), getFT3ChipPattern(), lr); -} - -//__________________________________________________________________________ -const char* GeometryTGeo::composeSymNameSensor(Int_t d, Int_t lr) -{ - return Form("%s/%s%d", composeSymNameChip(d, lr), getFT3SensorPattern(), lr); -} - -//__________________________________________________________________________ -void GeometryTGeo::fillMatrixCache(int mask) -{ - // populate matrix cache for requested transformations - // -} diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CMakeLists.txt b/Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CMakeLists.txt index 8295e490f4d7d..834f11f2cce16 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CMakeLists.txt @@ -11,7 +11,7 @@ o2_add_test_root_macro(CheckTracksALICE3.C PUBLIC_LINK_LIBRARIES O2::DataFormatsITS - O2::DataFormatsTRK + O2::DataFormatsTRKFT3 O2::ITStracking O2::SimulationDataFormat O2::DetectorsBase diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CheckTracksALICE3.C b/Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CheckTracksALICE3.C index 836327507018c..3e849171e8757 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CheckTracksALICE3.C +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/macros/CheckTracksALICE3.C @@ -12,6 +12,20 @@ /// \file CheckTracksALICE3.C /// \brief Quality assurance macro for TRK tracking +#ifndef ENABLE_UPGRADES +#include +#include + +void CheckTracksALICE3(std::string = "o2trac_trk.root", + std::string = "o2sim", + std::string = "o2clus_trk.root", + std::string = "trk_qa_output.root") +{ + std::cerr << "CheckTracksALICE3 requires a build with ENABLE_UPGRADES" << std::endl; +} + +#else + #if !defined(__CLING__) || defined(__ROOTCLING__) #include #include @@ -30,7 +44,7 @@ #include #include "DataFormatsITS/TrackITS.h" -#include "DataFormatsTRK/Cluster.h" +#include "DataFormatsTRKFT3/Cluster.h" #include "SimulationDataFormat/MCCompLabel.h" #include "SimulationDataFormat/MCTrack.h" #include "SimulationDataFormat/MCTruthContainer.h" @@ -130,7 +144,7 @@ void CheckTracksALICE3(std::string tracfile = "o2trac_trk.root", std::unordered_map particleClusterMap; static constexpr int nTRKLayers = 11; - std::array*, nTRKLayers> clustersPerLayer{}; + std::array*, nTRKLayers> clustersPerLayer{}; std::array*, nTRKLayers> clusterLabelsPerLayer{}; for (int iLayer = 0; iLayer < nTRKLayers; ++iLayer) { @@ -617,3 +631,5 @@ void CheckTracksALICE3(std::string tracfile = "o2trac_trk.root", delete clustersFile; delete tracFile; } + +#endif diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/CMakeLists.txt b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/CMakeLists.txt index 1dfcb7a22f725..68afd31835999 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/CMakeLists.txt @@ -16,14 +16,13 @@ endif() o2_add_library(ALICE3GlobalReconstruction TARGETVARNAME targetName SOURCES src/TimeFrame.cxx - $<$:src/TrackerACTS.cxx> PUBLIC_LINK_LIBRARIES O2::ITStracking O2::GPUCommon Microsoft.GSL::GSL O2::CommonConstants O2::DataFormatsITSMFT - O2::DataFormatsTRK + O2::DataFormatsTRKFT3 O2::SimulationDataFormat O2::ITSBase O2::ITSReconstruction diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameMixin.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameMixin.h index 6e95be32dd0e1..da1a80b77772b 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameMixin.h +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/include/ALICE3GlobalReconstruction/TimeFrameMixin.h @@ -17,8 +17,8 @@ #define ALICEO2_ALICE3GLOBALRECONSTRUCTION_TIMEFRAMEMIXIN_H #include "CommonDataFormat/InteractionRecord.h" -#include "DataFormatsTRK/Cluster.h" -#include "DataFormatsTRK/ROFRecord.h" +#include "DataFormatsTRKFT3/Cluster.h" +#include "DataFormatsTRKFT3/ROFRecord.h" #include "ITStracking/ROFLookupTables.h" #include "ITStracking/TimeFrame.h" #include "SimulationDataFormat/MCCompLabel.h" @@ -27,7 +27,7 @@ #include "SimulationDataFormat/DigitizationContext.h" #include "Steer/MCKinematicsReader.h" #include "TRKReconstruction/Clusterer.h" -#include "TRKSimulation/Hit.h" +#include "DataFormatsTRKFT3/Hit.h" #include "TRKBase/GeometryTGeo.h" #include "TRKBase/SegmentationChip.h" #include "Framework/Logger.h" @@ -58,8 +58,8 @@ class TimeFrameMixin : public Base int loadROFsFromHitTree(TTree* hitsTree, GeometryTGeo* gman, const nlohmann::json& config); - int loadROFrameData(const std::array, nLayers>& layerROFs, - const std::array, nLayers>& layerClusters, + int loadROFrameData(const std::array, nLayers>& layerROFs, + const std::array, nLayers>& layerClusters, const std::array, nLayers>& layerPatterns, const std::array*, nLayers>* mcLabels = nullptr, float yPlaneMLOT = 0.f); @@ -68,7 +68,7 @@ class TimeFrameMixin : public Base void addTruthSeedingVertices(); - void deriveAndInitTiming(const std::array, nLayers>& layerROFs); + void deriveAndInitTiming(const std::array, nLayers>& layerROFs); const o2::InteractionRecord& getTFAnchorIR() const noexcept { return mTFAnchorIR; } @@ -118,7 +118,7 @@ void TimeFrameMixin::initTimingTables(const std::array -void TimeFrameMixin::deriveAndInitTiming(const std::array, nLayers>& layerROFs) +void TimeFrameMixin::deriveAndInitTiming(const std::array, nLayers>& layerROFs) { if (mTimingTablesInitialised) { return; @@ -180,7 +180,7 @@ int TimeFrameMixin::loadROFsFromHitTree(TTree* hitsTree, Geometry gman->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L) | o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); - std::vector* trkHit = nullptr; + std::vector* trkHit = nullptr; hitsTree->SetBranchAddress("TRKHit", &trkHit); const int inROFpileup{config.contains("inROFpileup") ? config["inROFpileup"].get() : 1}; @@ -313,8 +313,8 @@ int TimeFrameMixin::loadROFsFromHitTree(TTree* hitsTree, Geometry } template -int TimeFrameMixin::loadROFrameData(const std::array, nLayers>& layerROFs, - const std::array, nLayers>& layerClusters, +int TimeFrameMixin::loadROFrameData(const std::array, nLayers>& layerROFs, + const std::array, nLayers>& layerClusters, const std::array, nLayers>& layerPatterns, const std::array*, nLayers>* mcLabels, float yPlaneMLOT) @@ -391,7 +391,7 @@ int TimeFrameMixin::loadROFrameData(const std::array 1 || c.disk != -1) { + if (c.subDetID < 0 || c.subDetID > 1) { continue; } @@ -403,7 +403,7 @@ int TimeFrameMixin::loadROFrameData(const std::arraygetMatrixL2G(c.chipID) * locXYZ; diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/CMakeLists.txt b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/CMakeLists.txt index 6a4994e11467b..7b5cd0e735802 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/CMakeLists.txt @@ -18,7 +18,7 @@ o2_add_library(ALICE3GlobalReconstructionWorkflow O2::GPUWorkflow O2::SimConfig O2::DataFormatsITSMFT - O2::DataFormatsTRK + O2::DataFormatsTRKFT3 O2::SimulationDataFormat O2::DPLUtils O2::TRKBase diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpecImpl.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpecImpl.h index f6221e485f369..8a2f162019de0 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpecImpl.h +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpecImpl.h @@ -15,8 +15,8 @@ #include "ALICE3GlobalReconstructionWorkflow/TrackerSpec.h" #include "CommonDataFormat/IRFrame.h" -#include "DataFormatsTRK/Cluster.h" -#include "DataFormatsTRK/ROFRecord.h" +#include "DataFormatsTRKFT3/Cluster.h" +#include "DataFormatsTRKFT3/ROFRecord.h" #include "DetectorsBase/GeometryManager.h" #include "Field/MagFieldParam.h" #include "Field/MagneticField.h" @@ -26,7 +26,7 @@ #include "SimulationDataFormat/MCEventHeader.h" #include "SimulationDataFormat/MCTruthContainer.h" #include "TRKBase/GeometryTGeo.h" -#include "TRKSimulation/Hit.h" +#include "DataFormatsTRKFT3/Hit.h" #include #include @@ -59,7 +59,7 @@ void TrackerDPL::runTracking(framework::ProcessingContext& pc, TimeFrameT& timeF TFile hitsFile(mHitRecoConfig["inputfiles"]["hits"].get().c_str(), "READ"); TFile mcHeaderFile(mHitRecoConfig["inputfiles"]["mcHeader"].get().c_str(), "READ"); TTree* hitsTree = hitsFile.Get("o2sim"); - std::vector* trkHit = nullptr; + std::vector* trkHit = nullptr; hitsTree->SetBranchAddress("TRKHit", &trkHit); TTree* mcHeaderTree = mcHeaderFile.Get("o2sim"); @@ -92,16 +92,16 @@ void TrackerDPL::runTracking(framework::ProcessingContext& pc, TimeFrameT& timeF TGeoGlobalMagField::Instance()->Lock(); constexpr int nLayers{11}; - std::array, nLayers> layerClusters; + std::array, nLayers> layerClusters; std::array, nLayers> layerPatterns; - std::array, nLayers> layerROFs; + std::array, nLayers> layerROFs; std::array*, nLayers> layerLabels{}; size_t nInputRofs{0}; for (int iLayer = 0; iLayer < nLayers; ++iLayer) { - layerClusters[iLayer] = pc.inputs().get>(std::format("compClusters_{}", iLayer)); + layerClusters[iLayer] = pc.inputs().get>(std::format("compClusters_{}", iLayer)); layerPatterns[iLayer] = pc.inputs().get>(std::format("patterns_{}", iLayer)); - layerROFs[iLayer] = pc.inputs().get>(std::format("ROframes_{}", iLayer)); + layerROFs[iLayer] = pc.inputs().get>(std::format("ROframes_{}", iLayer)); nInputRofs = std::max(nInputRofs, layerROFs[iLayer].size()); if (mIsMC) { layerLabels[iLayer] = pc.inputs().get*>(std::format("trkmclabels_{}", iLayer)).release(); @@ -173,7 +173,7 @@ void TrackerDPL::runTracking(framework::ProcessingContext& pc, TimeFrameT& timeF highestROF = std::max(highestROF, static_cast(clockLayer.getROF(vtx.getTimeStamp().lower()))); } - std::vector allTrackROFs(highestROF); + std::vector allTrackROFs(highestROF); for (size_t iROF = 0; iROF < allTrackROFs.size(); ++iROF) { auto& rof = allTrackROFs[iROF]; o2::InteractionRecord ir; diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx index 0b7ab97d44ee6..f588b07c598bf 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackerSpec.cxx @@ -19,8 +19,8 @@ #include "CommonUtils/DLLoaderBase.h" #include "CommonDataFormat/IRFrame.h" -#include "DataFormatsTRK/Cluster.h" -#include "DataFormatsTRK/ROFRecord.h" +#include "DataFormatsTRKFT3/Cluster.h" +#include "DataFormatsTRKFT3/ROFRecord.h" #include "DetectorsBase/GeometryManager.h" #include "ITStracking/TimeFrame.h" #include "ITStracking/Configuration.h" @@ -35,7 +35,7 @@ #include "SimulationDataFormat/MCTruthContainer.h" #include "TRKBase/GeometryTGeo.h" #include "TRKBase/SegmentationChip.h" -#include "TRKSimulation/Hit.h" +#include "DataFormatsTRKFT3/Hit.h" #include "ALICE3GlobalReconstruction/TimeFrame.h" #include "ALICE3GlobalReconstructionWorkflow/TrackerSpec.h" #include "ALICE3GlobalReconstructionWorkflow/TrackerSpecImpl.h" diff --git a/Detectors/Upgrades/ALICE3/IOTOF/base/include/IOTOFBase/GeometryTGeo.h b/Detectors/Upgrades/ALICE3/IOTOF/base/include/IOTOFBase/GeometryTGeo.h index d466cc2987f23..616c6409f4577 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/base/include/IOTOFBase/GeometryTGeo.h +++ b/Detectors/Upgrades/ALICE3/IOTOF/base/include/IOTOFBase/GeometryTGeo.h @@ -39,7 +39,7 @@ class GeometryTGeo : public o2::detectors::DetMatrixCache static const char* getIOTOFVolPattern() { return sIOTOFVolumeName.c_str(); } // Inner TOF - const int getITOFNumberOfChips() { return mNumberOfChipsIOTOF[0]; } + const int getITOFNumberOfChips() const { return mNumberOfChipsIOTOF[0]; } static const char* getITOFLayerPattern() { return sITOFLayerName.c_str(); } static const char* getITOFStavePattern() { return sITOFStaveName.c_str(); } static const char* getITOFModulePattern() { return sITOFModuleName.c_str(); } @@ -47,7 +47,7 @@ class GeometryTGeo : public o2::detectors::DetMatrixCache static const char* getITOFSensorPattern() { return sITOFSensorName.c_str(); } // Outer TOF - const int getOTOFNumberOfChips() { return mNumberOfChipsIOTOF[1]; } + const int getOTOFNumberOfChips() const { return mNumberOfChipsIOTOF[1]; } static const char* getOTOFLayerPattern() { return sOTOFLayerName.c_str(); } static const char* getOTOFStavePattern() { return sOTOFStaveName.c_str(); } static const char* getOTOFModulePattern() { return sOTOFModuleName.c_str(); } @@ -55,13 +55,13 @@ class GeometryTGeo : public o2::detectors::DetMatrixCache static const char* getOTOFSensorPattern() { return sOTOFSensorName.c_str(); } // Forward TOF - const int getFTOFNumberOfChips() { return mNumberOfChipsFTOF; } + const int getFTOFNumberOfChips() const { return mNumberOfChipsFTOF; } static const char* getFTOFLayerPattern() { return sFTOFLayerName.c_str(); } static const char* getFTOFChipPattern() { return sFTOFChipName.c_str(); } static const char* getFTOFSensorPattern() { return sFTOFSensorName.c_str(); } // Backward TOF - const int getBTOFNumberOfChips() { return mNumberOfChipsBTOF; } + const int getBTOFNumberOfChips() const { return mNumberOfChipsBTOF; } static const char* getBTOFLayerPattern() { return sBTOFLayerName.c_str(); } static const char* getBTOFChipPattern() { return sBTOFChipName.c_str(); } static const char* getBTOFSensorPattern() { return sBTOFSensorName.c_str(); } diff --git a/Detectors/Upgrades/ALICE3/IOTOF/base/include/IOTOFBase/IOTOFBaseParam.h b/Detectors/Upgrades/ALICE3/IOTOF/base/include/IOTOFBase/IOTOFBaseParam.h index 22454e0db0f48..f81f3f9484475 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/base/include/IOTOFBase/IOTOFBaseParam.h +++ b/Detectors/Upgrades/ALICE3/IOTOF/base/include/IOTOFBase/IOTOFBaseParam.h @@ -28,6 +28,8 @@ struct ChipSpecifics { float PassiveEdgeReadOut = 0.; float PassiveEdgeTop = 0.; float PassiveEdgeSide = 0.; + float PixelPassiveEdgeX = 0.; + float PixelPassiveEdgeZ = 0.; float SensorLayerThicknessEff = 0.; float SensorLayerThickness = 0.; @@ -41,10 +43,15 @@ struct ChipSpecifics { struct ITOFChipSpecifics : ChipSpecifics { ITOFChipSpecifics() { - NCols = 258; + NCols = 129; NRows = 271; PitchCol = 250.00e-4; PitchRow = 100.00e-4; + PassiveEdgeReadOut = 0.; + PassiveEdgeTop = 0.; + PassiveEdgeSide = 0.; + PixelPassiveEdgeX = 0.; + PixelPassiveEdgeZ = 0.; SensorLayerThicknessEff = 50.e-4; SensorLayerThickness = 50.e-4; } @@ -53,11 +60,15 @@ struct ITOFChipSpecifics : ChipSpecifics { struct OTOFChipSpecifics : ChipSpecifics { OTOFChipSpecifics() { - NCols = 517; + NCols = 125; NRows = 243; PitchCol = 250.00e-4; PitchRow = 100.00e-4; - PassiveEdgeSide = 106.48e-4; + PassiveEdgeTop = 50.e-4; + PassiveEdgeSide = 115.8e-4; + PassiveEdgeReadOut = 50.e-4; + PixelPassiveEdgeX = 0.; + PixelPassiveEdgeZ = 0.; SensorLayerThicknessEff = 50.e-4; SensorLayerThickness = 50.e-4; } diff --git a/Detectors/Upgrades/ALICE3/IOTOF/base/src/IOTOFBaseLinkDef.h b/Detectors/Upgrades/ALICE3/IOTOF/base/src/IOTOFBaseLinkDef.h index 5cbff299d78c1..e83f4fb51b130 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/base/src/IOTOFBaseLinkDef.h +++ b/Detectors/Upgrades/ALICE3/IOTOF/base/src/IOTOFBaseLinkDef.h @@ -15,8 +15,8 @@ #pragma link off all classes; #pragma link off all functions; -#pragma link C++ class o2::iotof::GeometryTGeo + +#pragma link C++ class o2::iotof::GeometryTGeo + ; #pragma link C++ class o2::iotof::IOTOFBaseParam + ; #pragma link C++ class o2::conf::ConfigurableParamHelper < o2::iotof::IOTOFBaseParam> + ; -#endif \ No newline at end of file +#endif diff --git a/Detectors/Upgrades/ALICE3/IOTOF/macros/CheckDigitsIOTOF.C b/Detectors/Upgrades/ALICE3/IOTOF/macros/CheckDigitsIOTOF.C index cd9e806ca24e7..26ffd08697d56 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/macros/CheckDigitsIOTOF.C +++ b/Detectors/Upgrades/ALICE3/IOTOF/macros/CheckDigitsIOTOF.C @@ -101,10 +101,6 @@ void CheckDigitsIOTOF(std::string digifile = "tf3digits.root", std::string hitfi auto* gman = o2::iotof::GeometryTGeo::Instance(); gman->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); - // IOTOF response plane y = half sensor thickness - float depthMax = iotofPars.sensorThickness; // fallback (no CCDB) - const float yPlaneIOTOF = depthMax / 2.; - // Hits TFile* hitFile = TFile::Open(hitfile.data()); TTree* hitTree = (TTree*)hitFile->Get("o2sim"); @@ -162,7 +158,6 @@ void CheckDigitsIOTOF(std::string digifile = "tf3digits.root", std::string hitfi Int_t subDetID = gman->getIOTOFLayer(iDetID); Float_t x = 0.f, y = 0.f, z = 0.f; - Float_t x_flat = 0.f, z_flat = 0.f; // Float_t t = (*digArr)[iDigit].getTime(); @@ -207,13 +202,9 @@ void CheckDigitsIOTOF(std::string digifile = "tf3digits.root", std::string hitfi locHS.SetCoordinates(xyzLocS.X(), xyzLocS.Y(), xyzLocS.Z()); o2::math_utils::Vector3D locHE; /// Hit, end pos locHE.SetCoordinates(xyzLocE.X(), xyzLocE.Y(), xyzLocE.Z()); - o2::math_utils::Vector3D locHF; - // IOTOF: Interpolate to response plane - float x0 = locHS.X(), y0 = locHS.Y(), z0 = locHS.Z(); - float dltx = locHE.X() - x0, dlty = locHE.Y() - y0, dltz = locHE.Z() - z0; - float r = (std::abs(dlty) > 1e-9f) ? (yPlaneIOTOF - y0) / dlty : 0.5f; - locH.SetCoordinates(x0 + r * dltx, yPlaneIOTOF, z0 + r * dltz); + // IOTOF: Interpolate to mid point + locH.SetCoordinates(0.5 * (locHS.X() + locHE.X()), 0.5 * (locHS.Y() + locHE.Y()), 0.5 * (locHS.Z() + locHE.Z())); int row = 0, col = 0; float xlc = 0., zlc = 0.; @@ -226,7 +217,7 @@ void CheckDigitsIOTOF(std::string digifile = "tf3digits.root", std::string hitfi row, col, /// row and col retrieved from the hit: hit global position -> hit local position -> detector position (row, col) locH.X(), locH.Z(), /// x and z of the hit in the local reference frame: hit global position -> hit local position xlc, zlc, /// x and z of the hit in the local frame: hit global position -> hit local position -> detector position (row, col) -> local position - locHS.X() - locD.X(), locHS.Z() - locD.Z()); /// difference in x and z between the hit and the digit in the local frame + locH.X() - locD.X(), locH.Z() - locD.Z()); /// difference in x and z between the hit and the digit in the local frame nt2->Fill(chipID, gloD.Z(), locHS.X() - locHE.X(), locHS.Z() - locHE.Z()); /// differences between local hit start and hit end positions } // end loop on digits array @@ -237,21 +228,21 @@ void CheckDigitsIOTOF(std::string digifile = "tf3digits.root", std::string hitfi auto canvXY = new TCanvas("canvXY", "", 1600, 800); canvXY->Divide(2, 1); canvXY->cd(1); - nt->Draw("y:x>>h_y_vs_x_IOTOF(1000, -100, 100, 1000, -100, 100)", "id >= 0 && id < 14352", "colz"); + nt->Draw("y:x>>h_y_vs_x_IOTOF(1000, -100, 100, 1000, -100, 100)", "id >= 0 && id < 53568", "colz"); canvXY->cd(2); - nt->Draw("y:z>>h_y_vs_z_IOTOF(1000, -400, 400, 1000, -100, 100)", "id >= 0 && id < 14352", "colz"); + nt->Draw("y:z>>h_y_vs_z_IOTOF(1000, -400, 400, 1000, -100, 100)", "id >= 0 && id < 53568", "colz"); canvXY->SaveAs("tf3digits_y_vs_x_vs_z.pdf"); // z distributions auto canvZ = new TCanvas("canvZ", "", 800, 800); canvZ->cd(); - nt->Draw("z>>h_z_IOTOF(500, -70, 70)", "id >= 0 && id < 14352 "); + nt->Draw("z>>h_z_IOTOF(500, -70, 70)", "id >= 0 && id < 53568 "); canvZ->SaveAs("tf3digits_z.pdf"); // dz distributions (difference between local position of digits and hits in x and z) auto canvdZ = new TCanvas("canvdZ", "", 800, 800); canvdZ->cd(); - nt->Draw("dz>>h_dz_ML(500, -0.05, 0.05)", "id >= 0 && id < 14352 "); + nt->Draw("dz>>h_dz_ML(500, -0.05, 0.05)", "id >= 0 && id < 53568 "); canvdZ->SaveAs("tf3digits_dz.pdf"); canvdZ->SaveAs("tf3digits_dz.root"); @@ -259,13 +250,13 @@ void CheckDigitsIOTOF(std::string digifile = "tf3digits.root", std::string hitfi auto canvdXdZ = new TCanvas("canvdXdZ", "", 1600, 800); canvdXdZ->Divide(2, 1); canvdXdZ->cd(1); - nt->Draw("dx:dz>>h_dx_vs_dz_ITOF(600, -0.03, 0.03, 600, -0.03, 0.03)", "id >= 0 && id < 960", "colz"); + nt->Draw("dx:dz>>h_dx_vs_dz_ITOF(600, -0.03, 0.03, 600, -0.03, 0.03)", "id >= 0 && id < 1920", "colz"); addTLines(0.01); auto h = (TH2F*)gPad->GetPrimitive("h_dx_vs_dz_ITOF"); Info("ITOF", "RMS(dx)=%.1f mu", h->GetRMS(2) * 1e4); Info("ITOF", "RMS(dz)=%.1f mu", h->GetRMS(1) * 1e4); canvdXdZ->cd(2); - nt->Draw("dx:dz>>h_dx_vs_dz_OTOF(600, -0.03, 0.03, 600, -0.03, 0.03)", "id >= 960 && id < 14352", "colz"); + nt->Draw("dx:dz>>h_dx_vs_dz_OTOF(600, -0.03, 0.03, 600, -0.03, 0.03)", "id >= 1920 && id < 53568", "colz"); addTLines(0.01); h = (TH2F*)gPad->GetPrimitive("h_dx_vs_dz_OTOF"); Info("OTOF", "RMS(dx)=%.1f mu", h->GetRMS(2) * 1e4); @@ -278,13 +269,13 @@ void CheckDigitsIOTOF(std::string digifile = "tf3digits.root", std::string hitfi canvdXdZHit->Divide(2, 1); canvdXdZHit->cd(1); LOG(info) << "dxH, dzH"; - nt2->Draw("dxH:dzH>>h_dxH_vs_dzH_ITOF(300, -0.03, 0.03, 300, -0.03, 0.03)", "id >= 0 && id < 960", "colz"); + nt2->Draw("dxH:dzH>>h_dxH_vs_dzH_ITOF(300, -0.03, 0.03, 300, -0.03, 0.03)", "id >= 0 && id < 1920", "colz"); addTLines(0.01); h = (TH2F*)gPad->GetPrimitive("h_dxH_vs_dzH_ITOF"); Info("ITOF", "RMS(dxH)=%.1f mu", h->GetRMS(2) * 1e4); Info("ITOF", "RMS(dzH)=%.1f mu", h->GetRMS(1) * 1e4); canvdXdZHit->cd(2); - nt2->Draw("dxH:dzH>>h_dxH_vs_dzH_OTOF(300, -0.03, 0.03, 300, -0.03, 0.03)", "id >= 960 && id < 14352", "colz"); + nt2->Draw("dxH:dzH>>h_dxH_vs_dzH_OTOF(300, -0.03, 0.03, 300, -0.03, 0.03)", "id >= 1920 && id < 53568", "colz"); addTLines(0.01); h = (TH2F*)gPad->GetPrimitive("h_dxH_vs_dzH_OTOF"); Info("OTOF", "RMS(dxH)=%.1f mu", h->GetRMS(2) * 1e4); diff --git a/Detectors/Upgrades/ALICE3/IOTOF/simulation/include/IOTOFSimulation/DPLDigitizerParam.h b/Detectors/Upgrades/ALICE3/IOTOF/simulation/include/IOTOFSimulation/DPLDigitizerParam.h index 66014e1c7f06e..784071701eef7 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/simulation/include/IOTOFSimulation/DPLDigitizerParam.h +++ b/Detectors/Upgrades/ALICE3/IOTOF/simulation/include/IOTOFSimulation/DPLDigitizerParam.h @@ -27,10 +27,13 @@ struct DPLDigitizerParam : public o2::conf::ConfigurableParamHelper (0.5 * specsConfig.PitchRow - specsConfig.PixelPassiveEdgeX) || std::abs(zCol - (iCol + 0.5) * specsConfig.PitchCol) > (0.5 * specsConfig.PitchCol - specsConfig.PixelPassiveEdgeZ)) { + iRow = iCol = -1; + return; + } if (xRow < 0) { iRow -= 1; } @@ -206,6 +211,11 @@ inline bool Segmentation::localToDetector(float xRow, float zCol, int& iRow, int } iRow = int(xRow / specsConfig.PitchRow); iCol = int(zCol / specsConfig.PitchCol); + // check pixel passive region + if (std::abs(xRow - (iRow + 0.5) * specsConfig.PitchRow) > (0.5 * specsConfig.PitchRow - specsConfig.PixelPassiveEdgeX) || std::abs(zCol - (iCol + 0.5) * specsConfig.PitchCol) > (0.5 * specsConfig.PitchCol - specsConfig.PixelPassiveEdgeZ)) { + iRow = iCol = -1; + return false; + } return true; } diff --git a/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Digitizer.cxx b/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Digitizer.cxx index f5694412c5f5e..dbcb306911847 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Digitizer.cxx +++ b/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Digitizer.cxx @@ -17,6 +17,7 @@ /// #include "IOTOFSimulation/Digitizer.h" +#include "IOTOFSimulation/DPLDigitizerParam.h" #include "DetectorsRaw/HBFUtils.h" #include @@ -30,7 +31,6 @@ namespace o2::iotof { o2::iotof::Segmentation* Digitizer::sSegmentation = nullptr; - //_______________________________________________________________________ void Digitizer::init() { @@ -50,10 +50,12 @@ void Digitizer::init() /// } } + const auto& digitizerParams = o2::iotof::DPLDigitizerParam::Instance(); + LOG(info) << "Initializing IOTOF digitizer"; - LOG(info) << " Time resolution: " << mTimeResolution * 1e3 << " ps"; - LOG(info) << " Charge threshold: " << mChargeThreshold << " electrons"; - LOG(info) << " Detection efficiency: " << mEfficiency * 100 << " %"; + LOG(info) << " Time resolution: " << digitizerParams.timeResolution * 1e3 << " ps"; + LOG(info) << " Charge threshold: " << digitizerParams.chargeThreshold << " electrons"; + LOG(info) << " Detection efficiency: " << digitizerParams.efficiency * 100 << " %"; LOG(info) << " Continuous mode: " << (mContinuous ? "ON" : "OFF"); sSegmentation = o2::iotof::Segmentation::Instance(); } @@ -100,7 +102,7 @@ void Digitizer::processHit(const o2::itsmft::Hit& hit, int evID, int srcID) } // Get detector element ID - int chipID = hit.GetDetectorID(); + const int chipID = hit.GetDetectorID(); auto& chip = mChips[chipID]; if (chip.isDisabled()) { LOG(debug) << "Hit rejected because chip " << chipID << " is disabled"; @@ -110,10 +112,12 @@ void Digitizer::processHit(const o2::itsmft::Hit& hit, int evID, int srcID) // Convert energy loss to charge (number of electrons) float energyLoss = hit.GetEnergyLoss(); // in GeV int charge = energyToCharge(energyLoss); + const auto& digitizerParams = o2::iotof::DPLDigitizerParam::Instance(); + int electronsPerStep = static_cast(charge / digitizerParams.nSimSteps); // Apply charge threshold - if (charge < mChargeThreshold) { - LOG(debug) << "Hit rejected by charge threshold: " << charge << " < " << mChargeThreshold; + if (charge < digitizerParams.chargeThreshold) { + LOG(debug) << "Hit rejected by charge threshold: " << charge << " < " << digitizerParams.chargeThreshold; return; } @@ -128,33 +132,135 @@ void Digitizer::processHit(const o2::itsmft::Hit& hit, int evID, int srcID) LOG(debug) << "Invalid detector ID: " << chipID << ", geometry size: " << mGeometry->getSize(); return; // invalid detector ID } + + // Create the digit with time information + o2::MCCompLabel label(hit.GetTrackID(), evID, srcID, false); + const int roFrameAbs = 0; // For now, we can set this to 0 or calculate based on time if needed + const int nROF = 1; // For now, we can assume the signal is contained in one ROF, this can be extended to multiple ROFs based on the time + + float** respMatrix = nullptr; + int rowStart = 0, colStart = 0, rowSpan = 0, colSpan = 0; + stepping(hit, respMatrix, rowStart, colStart, rowSpan, colSpan); + + for (int irow = rowSpan; irow--;) { + uint16_t rowIS = irow + rowStart; + for (int icol = colSpan; icol--;) { + uint16_t colIS = icol + colStart; + float nEleResp = respMatrix[irow][icol]; + if (!nEleResp) { + continue; + } + const int nElectronsSampled = gRandom->Poisson(electronsPerStep * nEleResp); + // Noise can be added here if needed + + registerDigits(chip, roFrameAbs, smearedTime, nROF, + static_cast(rowIS), static_cast(colIS), nElectronsSampled, label); + } + } + + for (int irow = 0; irow < rowSpan; ++irow) { + delete[] respMatrix[irow]; + } + delete[] respMatrix; +} + +void Digitizer::stepping(const o2::itsmft::Hit& hit, float**& respMatrix, int& rowStart, int& colStart, int& rowSpan, int& colSpan) +{ const auto& matrix = mGeometry->getMatrixL2G(hit.GetDetectorID()); + const int chipID = hit.GetDetectorID(); + const int subdetectorID = mGeometry->getIOTOFLayer(chipID); + + auto xyzPositionStart(matrix ^ (hit.GetPosStart())); // start position in sensor frame + auto xyzPositionEnd(matrix ^ (hit.GetPos())); // end position in sensor frame + + const auto& digitizerParams = o2::iotof::DPLDigitizerParam::Instance(); + const auto stepVector = (xyzPositionEnd - xyzPositionStart) / digitizerParams.nSimSteps; + xyzPositionStart = xyzPositionStart + stepVector * 0.5f; // center the start position in the middle of the step + xyzPositionEnd = xyzPositionEnd - stepVector * 0.5f; // center the end position in the middle of the step + + rowStart = -1; + colStart = -1; + int rowEnd = -1, colEnd = -1, nSkip = 0, nSteps = digitizerParams.nSimSteps; + while (!sSegmentation->localToDetector(xyzPositionStart.X(), xyzPositionStart.Z(), rowStart, colStart, mGeometry->getIOTOFLayer(chipID))) { + if (++nSkip > digitizerParams.nSimSteps) { // additional check to add: should we exclude something? + LOG(debug) << "Hit position out of bounds for detector ID " << chipID; + return; // hit is outside the active area + } + xyzPositionStart += stepVector; + } + + while (!sSegmentation->localToDetector(xyzPositionEnd.X(), xyzPositionEnd.Z(), rowEnd, colEnd, mGeometry->getIOTOFLayer(chipID))) { + if (++nSkip > digitizerParams.nSimSteps) { // additional check to add: should we exclude something? + LOG(debug) << "Hit position out of bounds for detector ID " << chipID; + return; // hit is outside the active area + } + xyzPositionEnd += stepVector; + } - math_utils::Vector3D xyzPositionStart(matrix ^ (hit.GetPosStart())); // start position in sensor frame - // math_utils::Vector3D xyzPositionEnd(matrix ^ (hit.GetPos())); // end position in sensor frame + if (rowStart > rowEnd) { + std::swap(rowStart, rowEnd); + } + if (colStart > colEnd) { + std::swap(colStart, colEnd); + } - int row = 0; // Will be determined from start hit position - int col = 0; // Will be determined from start hit position + // Expand the range to take into account the effects of charge sharing + rowStart -= digitizerParams.responseMatrixSize / 2; + rowEnd += digitizerParams.responseMatrixSize / 2; + rowStart = std::max(rowStart, 0); + colStart = std::max(colStart, 0); - if (!sSegmentation->localToDetector(xyzPositionStart.X(), xyzPositionStart.Z(), row, col, mGeometry->getIOTOFLayer(chipID))) { - LOG(debug) << "Hit position out of bounds for detector ID " << chipID; - return; // hit is outside the active area + rowEnd = std::min(rowEnd, (subdetectorID == 0 ? sSegmentation->mITofSpecsConfig.NRows : sSegmentation->mOTofSpecsConfig.NRows) - 1); + colEnd = std::min(colEnd, (subdetectorID == 0 ? sSegmentation->mITofSpecsConfig.NCols : sSegmentation->mOTofSpecsConfig.NCols) - 1); + rowSpan = rowEnd - rowStart + 1; + colSpan = colEnd - colStart + 1; + + respMatrix = new float*[rowSpan]; + for (int i = 0; i < rowSpan; ++i) { + respMatrix[i] = new float[colSpan](); } - // Create the digit with time information - o2::MCCompLabel label(hit.GetTrackID(), evID, srcID, false); - const int roFrameAbs = 0; // For now, we can set this to 0 or calculate based on time if needed - const int nROF = 1; // For now, we can assume the signal is contained in one ROF, this can be extended to multiple ROFs based on the time + int rowPrev = -1, colPrev = -1, row = 0, col = 0; + if (!respMatrix || rowSpan <= 0 || colSpan <= 0) { + return; + } + if (nSkip) { + nSteps -= nSkip; + } - registerDigits(chip, roFrameAbs, smearedTime, nROF, static_cast(row), static_cast(col), charge, label); + auto& currentPosLocal = xyzPositionStart; + for (int iStep = nSteps; iStep--;) { + sSegmentation->localToDetector(currentPosLocal.X(), currentPosLocal.Z(), row, col, subdetectorID); + if (row != rowPrev || col != colPrev) { + rowPrev = row; + colPrev = col; + } + + currentPosLocal += stepVector; // Move to the next step position + + for (int irow = digitizerParams.responseMatrixSize; irow--;) { + int rowDest = row + irow - (digitizerParams.responseMatrixSize / 2) - rowStart; // destination row in the respMatrix + if (rowDest < 0 || rowDest >= rowSpan) { + continue; + } + for (int icol = digitizerParams.responseMatrixSize; icol--;) { + int colDest = col + icol - (digitizerParams.responseMatrixSize / 2) - colStart; // destination column in the respMatrix + if (colDest < 0 || colDest >= colSpan) { + continue; + } + respMatrix[rowDest][colDest] += 1.; + } + } + } } //_______________________________________________________________________ double Digitizer::smearTime(double time) const { // Apply Gaussian smearing to simulate detector time resolution - if (mTimeResolution > 0) { - return time + gRandom->Gaus(0, mTimeResolution); + const auto& digitizerParams = o2::iotof::DPLDigitizerParam::Instance(); + if (digitizerParams.timeResolution > 0) { + return time + gRandom->Gaus(0, digitizerParams.timeResolution); } return time; } @@ -164,15 +270,17 @@ int Digitizer::energyToCharge(float energyLoss) const { // Convert energy loss (GeV) to number of electrons // Typical value: 3.6 eV per electron-hole pair in silicon - // energyLoss is in GeV, mEnergyToCharge is GeV per electron - return static_cast(energyLoss / mEnergyToCharge); + // energyLoss is in GeV, energyToNElectrons is electrons per GeV + const auto& digitizerParams = o2::iotof::DPLDigitizerParam::Instance(); + return static_cast(energyLoss * digitizerParams.energyToNElectrons); } //_______________________________________________________________________ bool Digitizer::isEfficient() const { // Apply efficiency cut using random number - return gRandom->Uniform() < mEfficiency; + const auto& digitizerParams = o2::iotof::DPLDigitizerParam::Instance(); + return gRandom->Uniform() < digitizerParams.efficiency; } //_______________________________________________________________________ @@ -181,6 +289,8 @@ void Digitizer::fillOutputContainer() LOG(info) << "Filling output container with digits from chips"; LOG(debug) << "Number of chips: " << mChips.size(); + const auto& digitizerParams = o2::iotof::DPLDigitizerParam::Instance(); + o2::itsmft::ROFRecord rof; rof.setFirstEntry(mDigits->size()); // index of the first digit @@ -200,10 +310,9 @@ void Digitizer::fillOutputContainer() auto& chipDigits = chip.getDigits(); for (const auto& [key, digit] : chipDigits) { - /// Charge threshold not implemented yet - /// if (digit.getCharge() < mChargeThreshold) { - /// continue; // skip digits below threshold - /// } + if (digit.getCharge() < digitizerParams.chargeThreshold) { + continue; // skip digits below threshold + } int digitID = mDigits->size(); mDigits->emplace_back(digit.getChipIndex(), digit.getRow(), digit.getColumn(), digit.getCharge(), digit.getTime()); diff --git a/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Layer.cxx b/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Layer.cxx index f2e42e1bce172..f63c93d37ce75 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Layer.cxx +++ b/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Layer.cxx @@ -178,7 +178,7 @@ void ITOFLayer::createLayer(TGeoVolume* motherVolume) setStaveStyle(staveVol); // Now we create the volume for a single module (sensor + chip) - const int modulesPerStaveX = 1; // we assume that each stave is divided in 2 modules along the x direction + const int modulesPerStaveX = 1; // we assume that each stave is divided in 1 modules along the x direction const double moduleSizeX = staveSizeX / modulesPerStaveX; // cm const double moduleSizeY = staveSizeY; // cm const double moduleSizeZ = staveSizeZ / mModulesPerStave; // cm @@ -188,7 +188,7 @@ void ITOFLayer::createLayer(TGeoVolume* motherVolume) // Now we create the volume of the chip, which is the same for all modules const int chipsPerModuleX = 2; // we assume that each module is divided in 2 chips along the x direction - const int chipsPerModuleZ = 2; // we assume that each module is divided in 2 chips along the z direction + const int chipsPerModuleZ = 4; // we assume that each module is divided in 2 chips along the z direction const double chipSizeX = moduleSizeX / chipsPerModuleX; // cm const double chipSizeY = moduleSizeY; // cm const double chipSizeZ = moduleSizeZ / chipsPerModuleZ; // cm @@ -324,28 +324,23 @@ void OTOFLayer::createLayer(TGeoVolume* motherVolume) setStaveStyle(staveVol); // Now we create the volume for a single module (sensor + chip) - // oTOF V2 is a 2xN matrix of modules per stave with overlap along z. + // oTOF V2 is a 2xN matrix. const int modulesPerStaveX = 2; if (mModulesPerStave % modulesPerStaveX != 0) { LOG(fatal) << "Invalid oTOF module layout: total modules per stave " << mModulesPerStave << " is not divisible by modulesPerStaveX=" << modulesPerStaveX; } - const int modulesPerStaveZ = mModulesPerStave / modulesPerStaveX; - const double moduleOverlapZ = 0.7; // cm, 7 mm longitudinal overlap from oTOF V2 specs + const int modulesPerStaveZ = 2 * mModulesPerStave / modulesPerStaveX; const double moduleSizeX = staveSizeX / modulesPerStaveX; const double moduleSizeY = staveSizeY; - const double moduleSizeZ = (staveSizeZ + (modulesPerStaveZ - 1) * moduleOverlapZ) / modulesPerStaveZ; - const double modulePitchZ = moduleSizeZ - moduleOverlapZ; - if (modulePitchZ <= 0.0) { - LOG(fatal) << "Invalid oTOF module overlap " << moduleOverlapZ << " cm for module size " << moduleSizeZ << " cm"; - } + const double moduleSizeZ = staveSizeZ / modulesPerStaveZ; TGeoBBox* module = new TGeoBBox(moduleSizeX * 0.5, moduleSizeY * 0.5, moduleSizeZ * 0.5); TGeoVolume* moduleVol = new TGeoVolume(moduleName, module, medAir); setModuleStyle(moduleVol); // Now we create the volume of the chip, which is the same for all modules const int chipsPerModuleX = 2; // we assume that each module is divided in 2 chips along the x direction - const int chipsPerModuleZ = 2; // we assume that each module is divided in 2 chips along the z direction + const int chipsPerModuleZ = 4; // we assume that each module is divided in 2 chips along the z direction const double chipSizeX = moduleSizeX / chipsPerModuleX; // cm const double chipSizeY = moduleSizeY; // cm const double chipSizeZ = moduleSizeZ / chipsPerModuleZ; // cm @@ -389,7 +384,7 @@ void OTOFLayer::createLayer(TGeoVolume* motherVolume) for (int j = 0; j < modulesPerStaveZ; ++j) { LOGP(info, "oTOF: Creating module {}/{} for stave {}/{}", i + 1, modulesPerStaveX, j + 1, modulesPerStaveZ); const double tx = (i + 0.5) * moduleSizeX - 0.5 * staveSizeX; - const double tz = -0.5 * staveSizeZ + 0.5 * moduleSizeZ + j * modulePitchZ; + const double tz = -0.5 * staveSizeZ + (j + 0.5) * moduleSizeZ; auto* translation = new TGeoTranslation(tx, 0, tz); staveVol->AddNode(moduleVol, 1 + i * modulesPerStaveZ + j, translation); } diff --git a/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Segmentation.cxx b/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Segmentation.cxx index ea03b3d317cdc..6e4624294a60e 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Segmentation.cxx +++ b/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Segmentation.cxx @@ -39,8 +39,8 @@ Segmentation::Segmentation() } else { auto& itofPars = ITOFChipSpecificParam::Instance(); auto& otofPars = OTOFChipSpecificParam::Instance(); - const ChipSpecifics mITofChipPars(itofPars.NCols, itofPars.NRows, itofPars.PitchCol, itofPars.PitchRow, itofPars.PassiveEdgeReadOut, itofPars.PassiveEdgeTop, itofPars.PassiveEdgeSide, itofPars.SensorLayerThicknessEff, itofPars.SensorLayerThickness); - const ChipSpecifics mOTofChipPars(otofPars.NCols, otofPars.NRows, otofPars.PitchCol, otofPars.PitchRow, otofPars.PassiveEdgeReadOut, otofPars.PassiveEdgeTop, otofPars.PassiveEdgeSide, otofPars.SensorLayerThicknessEff, otofPars.SensorLayerThickness); + const ChipSpecifics mITofChipPars(itofPars.NCols, itofPars.NRows, itofPars.PitchCol, itofPars.PitchRow, itofPars.PassiveEdgeReadOut, itofPars.PassiveEdgeTop, itofPars.PassiveEdgeSide, itofPars.PixelPassiveEdgeX, itofPars.PixelPassiveEdgeZ, itofPars.SensorLayerThicknessEff, itofPars.SensorLayerThickness); + const ChipSpecifics mOTofChipPars(otofPars.NCols, otofPars.NRows, otofPars.PitchCol, otofPars.PitchRow, otofPars.PassiveEdgeReadOut, otofPars.PassiveEdgeTop, otofPars.PassiveEdgeSide, otofPars.PixelPassiveEdgeX, otofPars.PixelPassiveEdgeZ, otofPars.SensorLayerThicknessEff, otofPars.SensorLayerThickness); configChip(mITofChipPars, 0 /* subDetectorID for iTOF */); configChip(mOTofChipPars, 1 /* subDetectorID for oTOF */); @@ -48,12 +48,12 @@ Segmentation::Segmentation() } void Segmentation::configChip(const int nCols, const int nRows, const float pitchCol, const float pitchRow, const float passiveEdgeReadOut, - const float passiveEdgeTop, const float passiveEdgeSide, const float sensorLayerThicknessEff, const float sensorLayerThickness, const int subDetectorID) + const float passiveEdgeTop, const float passiveEdgeSide, const float pixelPassiveEdgeX, const float pixelPassiveEdgeZ, const float sensorLayerThicknessEff, const float sensorLayerThickness, const int subDetectorID) { if (subDetectorID == 0) { - mITofSpecsConfig = ChipSpecifics(nCols, nRows, pitchCol, pitchRow, passiveEdgeReadOut, passiveEdgeTop, passiveEdgeSide, sensorLayerThicknessEff, sensorLayerThickness); + mITofSpecsConfig = ChipSpecifics(nCols, nRows, pitchCol, pitchRow, passiveEdgeReadOut, passiveEdgeTop, passiveEdgeSide, pixelPassiveEdgeX, pixelPassiveEdgeZ, sensorLayerThicknessEff, sensorLayerThickness); } else if (subDetectorID == 1) { - mOTofSpecsConfig = ChipSpecifics(nCols, nRows, pitchCol, pitchRow, passiveEdgeReadOut, passiveEdgeTop, passiveEdgeSide, sensorLayerThicknessEff, sensorLayerThickness); + mOTofSpecsConfig = ChipSpecifics(nCols, nRows, pitchCol, pitchRow, passiveEdgeReadOut, passiveEdgeTop, passiveEdgeSide, pixelPassiveEdgeX, pixelPassiveEdgeZ, sensorLayerThicknessEff, sensorLayerThickness); } else { printf("Invalid subDetectorID %d. Must be 0 (iTOF) or 1 (oTOF). No configuration applied.\n", subDetectorID); } diff --git a/Detectors/Upgrades/ALICE3/README.md b/Detectors/Upgrades/ALICE3/README.md index 6ff034facb546..b7b712d8f5df6 100644 --- a/Detectors/Upgrades/ALICE3/README.md +++ b/Detectors/Upgrades/ALICE3/README.md @@ -25,7 +25,7 @@ A list of the available DetIDs is reproted in the table below: | `A3IP` | Beam pipe | | `TRK` | Barrel Tracker | | `TF3` | Time Of Flight detectors | -| `FT3` | Forward endcaps | +| `FT3` | Obsolete: Forward endcaps are included in TRK | | `RCH` | Ring Imaging Cherenkov detectors | | `ECL` | Electromagnetic Calorimeter | | `MI3` | Muon Identification | @@ -68,8 +68,8 @@ Configurables for various sub-detectors are presented in the following Table: | Available options | Link to options | | ----------------- | ---------------------------------------------------------------- | -| TRK | [Link to TRK options](./TRK/README.md#specific-detector-setup) | -| FT3 | [Link to FT3 options](./FT3/README.md#specific-detector-setup) | +| TRK | [Link to TRK options](./TRKFT3/TRK/README.md#specific-detector-setup) | +| FT3 | [Link to FT3 options](./TRKFT3/FT3/README.md#specific-detector-setup) | | TOF | [Link to TOF options](./IOTOF/README.md#specific-detector-setup) | Example O2 command to create a geometry with **segmented layers for TRK (expect for VD), FT3 and TOF:** diff --git a/Detectors/Upgrades/ALICE3/RICH/base/include/RICHBase/RICHBaseParam.h b/Detectors/Upgrades/ALICE3/RICH/base/include/RICHBase/RICHBaseParam.h index a9f2f7fbba5d1..2d1d83d376ea3 100644 --- a/Detectors/Upgrades/ALICE3/RICH/base/include/RICHBase/RICHBaseParam.h +++ b/Detectors/Upgrades/ALICE3/RICH/base/include/RICHBase/RICHBaseParam.h @@ -20,34 +20,89 @@ namespace o2 namespace rich { struct RICHBaseParam : public o2::conf::ConfigurableParamHelper { - float zBaseSize = 18.6; // cm (18.4 in v3) - float rMax = 131.0; // cm (117.0 in v3) - float rMin = 104.0; // cm (90.0 in v3) - float radiatorThickness = 2.0; // cm - float detectorThickness = 0.2; // cm - float zRichLength = 700.0; // cm - int nRings = 11; // (25 in v3) - int nTiles = 44; // (36 in v3) - bool oddGeom = true; // (false in v3) - - // FWD and BWD RICH - bool enableFWDRich = false; - bool enableBWDRich = false; + double zBaseSize = 18.6; // cm (18.4 in v3) + double rMax = 131.0; // cm (117.0 in v3) + double rMin = 104.0; // cm (90.0 in v3) + double radiatorThickness = 2.0; // cm + double zRichLength = 700.0; // cm + int nRings = 11; // (25 in v3) + int nTiles = 44; // (36 in v3) + bool oddGeom = true; // (false in v3) - float rFWDMin = 13.7413f; - float rFWDMax = 103.947f; + // The active and passive silicon thicknesses must sum to detectorThickness. + double siliconeLayerThickness = 0.010; // cm: 0.1 mm resin layer in front + double detectorThickness = 0.1; // cm + double activeSiliconThickness = 0.01; // cm: 0.1 mm sensitive silicon + // double passiveSiliconThickness = 0.09f; // cm: (detectorThickness - activeSiliconThickness) - // Aerogel: - float zAerogelMin = 375.f; - float zAerogelMax = 377.f; + // cylindrical aerogel layout + bool useCylindricalAerogel = true; + double cylindricalAerogelEtaRef = 0.85; - // Argon: - float zArgonMin = 377.f; - float zArgonMax = 407.f; + // Enable geometry with rectangular modules + bool useRectangularModules = true; + + // Barrel photosensor active area. + double sipmActiveSizeZ = 18.0; // cm + double sipmActiveSizeRPhi = 17.0; // cm + + // Gas refractive index (then scaled with chromaticity) + double nGasEffective = 1.0006; + + // Aerogel refractive index (then scaled with chromaticity) + double nAerogelEffective = 1.03; + + // Parameters for geometry with quadrants + bool flagUseQuadrants = false; + // Opening between adjacent vessel quadrants, measured as a chord at shieldRMin. + double vesselPhiGap = 1.0; // cm + // Thickness of each lateral insulating wall at a quadrant boundary. + double vesselThicknessShieldingLateral = 1.0; // cm + // Rectangular size could be smaller with quadrants (< 17 cm depending on wall thickness) + double quadrantModuleSizeRPhi = 16.5; // cm + // Readout stack behind each SiPM plane, thicknesses along the local outward normal. + double pcb1Thickness = 0.4; // cm + double coolingPlateThickness = 0.4; // cm + double pcb2Thickness = 0.4; // cm + double pcb3Thickness = 0.4; // cm + // Surface-to-surface gaps between consecutive layers. + double gapSiPMToPCB1 = 0.10; // cm + double gapPCB1ToCoolingPlate = 0.10; // cm + double gapCoolingPlateToPCB2 = 0.10; // cm + double gapPCB2ToPCB3 = 0.10; // cm + + // Minimum edge-to-edge clearances used to avoid exact contacts between adjacent modules. + double moduleClearanceZ = 0.02; // cm + double moduleClearanceRPhi = 0.02; // cm + + // Shielding: + // Radial boundaries of the complete cylindrical enclosure. + double shieldRMin = 100.0; + double shieldRMax = 136.0; + // Radial thickness of the inner insulating wall. + double innerWallThickness = 2.0; + // Radial thickness of the outer insulating wall. + double outerWallThickness = 2.0; + // Full longitudinal length of the cylindrical side walls. + double shieldLengthZ = 220.0; + // Thickness of each insulating end cap along Z. + double endCapThicknessZ = 2.0; + + // FWD and BWD RICH (legacy) + bool enableFWDRich = false; + bool enableBWDRich = false; + double rFWDMin = 13.7413; + double rFWDMax = 103.947; + // Aerogel: + double zAerogelMin = 375.; + double zAerogelMax = 377.; + // Argon: + double zArgonMin = 377.; + double zArgonMax = 407.; // Detector: - float zSiliconMin = 407.f; - float zSiliconMax = 407.2f; + double zSiliconMin = 407.; + double zSiliconMax = 407.2; O2ParamDef(RICHBaseParam, "RICHBase"); }; @@ -55,4 +110,4 @@ struct RICHBaseParam : public o2::conf::ConfigurableParamHelper { } // namespace rich } // end namespace o2 -#endif \ No newline at end of file +#endif diff --git a/Detectors/Upgrades/ALICE3/RICH/simulation/include/RICHSimulation/RICHRing.h b/Detectors/Upgrades/ALICE3/RICH/simulation/include/RICHSimulation/RICHRing.h index 296e24cbd8f06..a1b8974c0b748 100644 --- a/Detectors/Upgrades/ALICE3/RICH/simulation/include/RICHSimulation/RICHRing.h +++ b/Detectors/Upgrades/ALICE3/RICH/simulation/include/RICHSimulation/RICHRing.h @@ -35,20 +35,20 @@ class Ring // z_ph: z position of the photosensitive surface (from the center) Ring(int rPosId, int nTilesPhi, - float rMin, - float rMax, - float radThick, - float radYmin, - float radYmax, - float radZ, - float photThick, - float photYmin, - float photYmax, - float photZ, - float radRad0, - float photRad0, - float aerDetDistance, - float thetaB, + double rMin, + double rMax, + double radThick, + double radYmin, + double radYmax, + double radZ, + double photThick, + double photYmin, + double photYmax, + double photZ, + double radRad0, + double photRad0, + double aerDetDistance, + double thetaB, const std::string motherName = "RICHV"); ~Ring() = default; @@ -60,10 +60,10 @@ class Ring private: int mPosId; // id of the ring int mNTiles; // number of modules - float mRRad; // max distance for radiators - float mRPhot; // max distance for photosensitive surfaces - float mRadThickness; // thickness of the radiator - float mPhotThickness; // thickness of the photosensitive surface + double mRRad; // max distance for radiators + double mRPhot; // max distance for photosensitive surfaces + double mRadThickness; // thickness of the radiator + double mPhotThickness; // thickness of the photosensitive surface ClassDef(Ring, 0); }; @@ -74,32 +74,32 @@ class FWDRich public: FWDRich() = default; FWDRich(std::string name, - float rMin, - float rMax, - float zAerogelMin, - float dZAerogel, - float zArgonMin, - float dZArgon, - float zSiliconMin, - float dZSilicon); + double rMin, + double rMax, + double zAerogelMin, + double dZAerogel, + double zArgonMin, + double dZArgon, + double zSiliconMin, + double dZSilicon); void createFWDRich(TGeoVolume* motherVolume); protected: std::string mName; - float mRmin; - float mRmax; + double mRmin; + double mRmax; // Aerogel: - float mZAerogelMin; - float mDZAerogel; + double mZAerogelMin; + double mDZAerogel; // Argon: - float mZArgonMin; - float mDZArgon; + double mZArgonMin; + double mDZArgon; // Silicon: - float mZSiliconMin; - float mDZSilicon; + double mZSiliconMin; + double mDZSilicon; ClassDef(FWDRich, 0); }; @@ -109,32 +109,32 @@ class BWDRich public: BWDRich() = default; BWDRich(std::string name, - float rMin, - float rMax, - float zAerogelMin, - float dZAerogel, - float zArgonMin, - float dZArgon, - float zSiliconMin, - float dZSilicon); + double rMin, + double rMax, + double zAerogelMin, + double dZAerogel, + double zArgonMin, + double dZArgon, + double zSiliconMin, + double dZSilicon); void createBWDRich(TGeoVolume* motherVolume); protected: std::string mName; - float mRmin; - float mRmax; + double mRmin; + double mRmax; // Aerogel: - float mZAerogelMin; - float mDZAerogel; + double mZAerogelMin; + double mDZAerogel; // Argon: - float mZArgonMin; - float mDZArgon; + double mZArgonMin; + double mDZArgon; // Silicon: - float mZSiliconMin; - float mDZSilicon; + double mZSiliconMin; + double mDZSilicon; ClassDef(BWDRich, 0); }; diff --git a/Detectors/Upgrades/ALICE3/RICH/simulation/src/Detector.cxx b/Detectors/Upgrades/ALICE3/RICH/simulation/src/Detector.cxx index 02719d6f93a00..0dd938b931665 100644 --- a/Detectors/Upgrades/ALICE3/RICH/simulation/src/Detector.cxx +++ b/Detectors/Upgrades/ALICE3/RICH/simulation/src/Detector.cxx @@ -15,6 +15,9 @@ #include #include #include +#include +#include +#include #include "DetectorsBase/Stack.h" #include "ITSMFTSimulation/Hit.h" @@ -27,6 +30,49 @@ namespace o2 { namespace rich { +namespace // quadrant equation solver +{ +double quadrantDeltaPhiEquation(double x, int nTilesPhi, double rMin, double totalBoundaryWidth) +{ + const double argument = totalBoundaryWidth * TMath::Cos(x / 2.0) / (2.0 * rMin); + if (TMath::Abs(argument) >= 1.0) { + return std::numeric_limits::quiet_NaN(); + } + const double rhs = 2.0 * TMath::Pi() / static_cast(nTilesPhi) - (8.0 / static_cast(nTilesPhi)) * TMath::ASin(argument); + return rhs - x; +} + +double solveQuadrantDeltaPhi(int nTilesPhi, double rMin, double totalBoundaryWidth) +{ + double lower = 0.0; + double upper = 1.1 * 2.0 * TMath::Pi() / static_cast(nTilesPhi); + double fLower = quadrantDeltaPhiEquation(lower, nTilesPhi, rMin, totalBoundaryWidth); + double fUpper = quadrantDeltaPhiEquation(upper, nTilesPhi, rMin, totalBoundaryWidth); + if (!std::isfinite(fLower) || !std::isfinite(fUpper) || fLower * fUpper > 0.0) { + return -1.0; + } + constexpr double tolerance = 1.0e-12; + constexpr int maxIterations = 200; + for (int iteration = 0; iteration < maxIterations; iteration++) { + const double middle = 0.5 * (lower + upper); + const double fMiddle = quadrantDeltaPhiEquation(middle, nTilesPhi, rMin, totalBoundaryWidth); + if (!std::isfinite(fMiddle)) { + return -1.0; + } + if (TMath::Abs(fMiddle) < tolerance || 0.5 * (upper - lower) < tolerance) { + return middle; + } + if (fLower * fMiddle < 0.0) { + upper = middle; + fUpper = fMiddle; + } else { + lower = middle; + fLower = fMiddle; + } + } + return 0.5 * (lower + upper); +} +} // namespace Detector::Detector() : o2::base::DetImpl("RCH", true), @@ -61,6 +107,10 @@ void Detector::ConstructGeometry() void Detector::createMaterials() { + auto& richPars = RICHBaseParam::Instance(); + const double nGasEffective = richPars.nGasEffective; + const double nAerogelEffective = richPars.nAerogelEffective; + int ifield = 2; // ? float fieldm = 10.0; // ? o2::base::Detector::initFieldTrackingParams(ifield, fieldm); @@ -89,12 +139,75 @@ void Detector::createMaterials() float epsilAerogel = 1.0E-4; // .10000E+01; float stminAerogel = 0.0; // cm "Default value used" + float tmaxfdCO2 = 0.1; // .10000E+01; // Degree + float stemaxCO2 = .10000E+01; // cm + float deemaxCO2 = 0.1; // 0.30000E-02; // Fraction of particle's energy 0SetCerenkov(globalMediumID(2, "AEROGEL"), nAerogelRindex, aerogelRindexEnergyGeV, aerogelAbsorptionOnRindexGrid, aerogelDetectionEfficiency, aerogelRindex); + // + // constexpr int nAerogelAbsorption = 2; + // double aerogelAbsorptionEnergyGeV[nAerogelAbsorption] = {1.0 * eVInGeV, 8.26561 * eVInGeV}; + // double aerogelAbsorptionLengthCm[nAerogelAbsorption] = {aerogelAbsorptionLengthCm, aerogelAbsorptionLengthCm}; + // mc->SetMaterialProperty(globalMediumID(2, "AEROGEL"), "ABSLENGTH", nAerogelAbsorption, aerogelAbsorptionEnergyGeV, aerogelAbsorptionLengthCm); + // + constexpr int nAerogelRayleigh = 22; + double aerogelRayleighEnergyGeV[nAerogelRayleigh] = {1.00 * eVInGeV, 1.06 * eVInGeV, 1.12 * eVInGeV, 1.18 * eVInGeV, 1.23984 * eVInGeV, 1.3051 * eVInGeV, 1.3776 * eVInGeV, 1.45864 * eVInGeV, 1.5498 * eVInGeV, 1.65312 * eVInGeV, 1.7712 * eVInGeV, 1.90745 * eVInGeV, 2.0664 * eVInGeV, 2.25426 * eVInGeV, 2.47968 * eVInGeV, 2.7552 * eVInGeV, 3.0996 * eVInGeV, 3.54241 * eVInGeV, 4.13281 * eVInGeV, 4.95937 * eVInGeV, 6.19921 * eVInGeV, 8.26561 * eVInGeV}; + double aerogelRayleighLengthCm[nAerogelRayleigh] = {543.253684, 430.307801, 345.247537, 280.204207, 229.885, 187.243, 150.828, 120.001, 94.1609, 72.7371, 55.1954, 41.0359, 29.7931, 21.0359, 14.3678, 9.42672, 5.88506, 3.44971, 1.86207, 0.897989, 0.367816, 0.116379}; + mc->SetMaterialProperty(globalMediumID(2, "AEROGEL"), "RAYLEIGH", nAerogelRayleigh, aerogelRayleighEnergyGeV, aerogelRayleighLengthCm); + + /// GAS + constexpr int nCO2Optical = 2; + double co2EnergyGeV[nCO2Optical] = {1.0 * eVInGeV, 8.26561 * eVInGeV}; + double co2Rindex[nCO2Optical] = {nGasEffective, nGasEffective}; // <- Target gas index for dielectrons + double co2AbsorptionLengthCm[nCO2Optical] = {1.0e5, 1.0e5}; + double co2DetectionEfficiency[nCO2Optical] = {0.0, 0.0}; + mc->SetCerenkov(globalMediumID(5, "CO2"), nCO2Optical, co2EnergyGeV, co2AbsorptionLengthCm, co2DetectionEfficiency, co2Rindex); + + /// SiO2 + constexpr int nSiO2Optical = 2; + double sio2EnergyGeV[nSiO2Optical] = {1.0 * eVInGeV, 8.26561 * eVInGeV}; + double sio2Rindex[nSiO2Optical] = {1.47, 1.47}; + double sio2AbsorptionLengthCm[nSiO2Optical] = {1.0e5, 1.0e5}; + double sio2DetectionEfficiency[nSiO2Optical] = {0.0, 0.0}; + mc->SetCerenkov(globalMediumID(9, "SIO2"), nSiO2Optical, sio2EnergyGeV, sio2AbsorptionLengthCm, sio2DetectionEfficiency, sio2Rindex); + + /// Silicone resin + constexpr int nSiliconeOptical = 2; + double siliconeEnergyGeV[nSiliconeOptical] = {1.0 * eVInGeV, 8.26561 * eVInGeV}; + double siliconeRindex[nSiliconeOptical] = {1.41, 1.41}; + double siliconeAbsorptionLengthCm[nSiliconeOptical] = {1.0e5, 1.0e5}; + double siliconeDetectionEfficiency[nSiliconeOptical] = {0.0, 0.0}; + mc->SetCerenkov(globalMediumID(10, "SILICONE"), nSiliconeOptical, siliconeEnergyGeV, siliconeAbsorptionLengthCm, siliconeDetectionEfficiency, siliconeRindex); + + /// Si (assuming same index as silicone resin as reflection losses are already included in PDE) + constexpr int nSiliconOptical = 2; + double siliconEnergyGeV[nSiliconOptical] = {1.0 * eVInGeV, 8.26561 * eVInGeV}; + double siliconRindex[nSiliconOptical] = {1.41, 1.41}; + double siliconAbsorptionLengthCm[nSiliconOptical] = {1.0e5, 1.0e5}; + double siliconDetectionEfficiency[nSiliconOptical] = {0.0, 0.0}; + mc->SetCerenkov(globalMediumID(3, "SILICON"), nSiliconOptical, siliconEnergyGeV, siliconAbsorptionLengthCm, siliconDetectionEfficiency, siliconRindex); + + // Si: outer layer just for photon absorption + constexpr int nSiliconAbsorberOptical = 2; + double siliconAbsorberEnergyGeV[nSiliconAbsorberOptical] = {1.0 * eVInGeV, 8.26561 * eVInGeV}; + double siliconAbsorberAbsorptionLengthCm[nSiliconAbsorberOptical] = {1.0e-7, 1.0e-7}; // 1 nm + mc->SetMaterialProperty(globalMediumID(11, "SILICON_ABSORBER"), "ABSLENGTH", nSiliconAbsorberOptical, siliconAbsorberEnergyGeV, siliconAbsorberAbsorptionLengthCm); } void Detector::createGeometry() @@ -145,8 +429,237 @@ void Detector::createGeometry() vRICH->SetTitle(vstrng); auto& richPars = RICHBaseParam::Instance(); + // Quadrant parameters + const bool flagUseQuadrants = richPars.flagUseQuadrants; + const double vesselPhiGap = richPars.vesselPhiGap; + const double vesselThicknessShieldingLateral = richPars.vesselThicknessShieldingLateral; + + // shielding parameters + double shieldRMin = richPars.shieldRMin; + double shieldRMax = richPars.shieldRMax; + double innerWallThickness = richPars.innerWallThickness; + double outerWallThickness = richPars.outerWallThickness; + double shieldLengthZ = richPars.shieldLengthZ; + double endCapThicknessZ = richPars.endCapThicknessZ; + + if (innerWallThickness <= 0.0 || outerWallThickness <= 0.0 || endCapThicknessZ <= 0.0 || shieldLengthZ <= 0.0) { + LOGP(fatal, "RICH shielding dimensions must be positive"); + } + + if (shieldRMin + innerWallThickness >= shieldRMax - outerWallThickness) { + LOGP(fatal, + "RICH shielding walls overlap: inner outer radius = {}, outer inner radius = {}", + shieldRMin + innerWallThickness, + shieldRMax - outerWallThickness); + } + + if (flagUseQuadrants) { + if (richPars.nTiles <= 0 || richPars.nTiles % 4 != 0) { + LOGP(fatal, "RICH quadrant geometry requires nTiles to be positive and divisible by four; received {}", richPars.nTiles); + } + if (vesselPhiGap < 0.0 || vesselThicknessShieldingLateral <= 0.0) { + LOGP(fatal, "RICH quadrant gap must be non-negative and lateral shielding thickness must be positive"); + } + const double totalBoundaryWidth = 2.0 * vesselThicknessShieldingLateral + vesselPhiGap; + if (totalBoundaryWidth >= 2.0 * richPars.rMin || vesselPhiGap >= 2.0 * shieldRMin) { + LOGP(fatal, "RICH quadrant boundary dimensions are incompatible with rMin={} cm and shieldRMin={} cm", richPars.rMin, shieldRMin); + } + const double quadrantDeltaPhi = solveQuadrantDeltaPhi(richPars.nTiles, richPars.rMin, totalBoundaryWidth); + if (!(quadrantDeltaPhi > 0.0)) { + LOGP(fatal, "RICH could not solve the quadrant module angular pitch"); + } + } + + // Name of the gas mother volume. This name will also be passed + // to each Ring so that the ring components become its daughters. + const char* richGasMotherName = "RICH_GAS_MOTHER"; + + TGeoMedium* medCO2 = gGeoManager->GetMedium("RCH_CO2$"); + if (!medCO2) { + LOGP(fatal, "RICH: CO2 medium not found"); + } + + TGeoMedium* medPeek = gGeoManager->GetMedium("RCH_PEEK$"); + if (!medPeek) { + LOGP(fatal, "RICH: PEEK medium not found"); + } + + TGeoMedium* medArmaFlex = gGeoManager->GetMedium("RCH_ARMAFLEX$"); + if (!medArmaFlex) { + LOGP(fatal, "RICH: ArmaFlex medium not found"); + } + + TGeoMedium* medArmaGel = gGeoManager->GetMedium("RCH_ARMAGEL$"); + if (!medArmaGel) { + LOGP(fatal, "RICH: ArmaGel medium not found"); + } + prepareLayout(); // Preparing the positions of the rings and tiles + // The gas mother includes the side-wall region and both end caps. ( as vessel ) + const double gasEnvelopeLengthZ = shieldLengthZ + 2.0 * endCapThicknessZ; + auto* gasEnvelopeShape = new TGeoTube(shieldRMin, shieldRMax, gasEnvelopeLengthZ / 2.0); + auto* gasEnvelopeVolume = new TGeoVolume(richGasMotherName, gasEnvelopeShape, medCO2); + + gasEnvelopeVolume->SetLineColor(kBlue - 9); + gasEnvelopeVolume->SetTransparency(90); + + // The gas envelope is a daughter of the general RICH volume. + vRICH->AddNode(gasEnvelopeVolume, 1, new TGeoTranslation(0.0, 0.0, 0.0)); + + if (!flagUseQuadrants) { + // ============================================================ + // Inner cylindrical insulating wall + // + // Radial interval: + // shieldRMin --> shieldRMin + innerWallThickness + // + // Longitudinal interval: + // -shieldLengthZ/2 --> +shieldLengthZ/2 + // ============================================================ + auto* innerWallShape = new TGeoTube(shieldRMin, shieldRMin + innerWallThickness, shieldLengthZ / 2.0); + auto* innerWallVolume = new TGeoVolume("RICH_SHIELD_INNER_WALL", innerWallShape, medArmaGel); + + innerWallVolume->SetLineColor(kOrange - 8); // kGray + innerWallVolume->SetTransparency(0); // 80 + gasEnvelopeVolume->AddNode(innerWallVolume, 1, new TGeoTranslation(0.0, 0.0, 0.0)); + + // ============================================================ + // Outer cylindrical insulating wall + // + // Radial interval: + // shieldRMax - outerWallThickness --> shieldRMax + // + // Longitudinal interval: + // -shieldLengthZ/2 --> +shieldLengthZ/2 + // ============================================================ + + auto* outerWallShape = new TGeoTube(shieldRMax - outerWallThickness, shieldRMax, shieldLengthZ / 2.0); + auto* outerWallVolume = new TGeoVolume("RICH_SHIELD_OUTER_WALL", outerWallShape, medArmaGel); + + outerWallVolume->SetLineColor(kOrange - 8); // kGray + outerWallVolume->SetTransparency(0); // 80 + gasEnvelopeVolume->AddNode(outerWallVolume, 1, new TGeoTranslation(0.0, 0.0, 0.0)); + + // ============================================================ + // Insulating end caps + // + // Each end cap covers: + // shieldRMin --> shieldRMax + // + // Each has full thickness: + // endCapThicknessZ + // ============================================================ + + auto* endCapShape = new TGeoTube(shieldRMin, shieldRMax, endCapThicknessZ / 2.0); + auto* endCapPlusVolume = new TGeoVolume("RICH_SHIELD_ENDCAP_PLUS", endCapShape, medArmaGel); + auto* endCapMinusVolume = new TGeoVolume("RICH_SHIELD_ENDCAP_MINUS", endCapShape, medArmaGel); + + endCapPlusVolume->SetLineColor(kOrange - 8); // kGray + endCapPlusVolume->SetTransparency(0); // 80 + + endCapMinusVolume->SetLineColor(kOrange - 8); // kGray + endCapMinusVolume->SetTransparency(0); // 80 + + const double endCapCenterZ = shieldLengthZ / 2.0 + endCapThicknessZ / 2.0; + + gasEnvelopeVolume->AddNode(endCapPlusVolume, 1, new TGeoTranslation(0.0, 0.0, endCapCenterZ)); + gasEnvelopeVolume->AddNode(endCapMinusVolume, 1, new TGeoTranslation(0.0, 0.0, -endCapCenterZ)); + } else { + // ============================================================ + // Four independent insulating vessel quadrants + // ============================================================ + const double totalBoundaryWidth = 2.0 * vesselThicknessShieldingLateral + vesselPhiGap; + const double moduleDeltaPhi = solveQuadrantDeltaPhi(richPars.nTiles, richPars.rMin, totalBoundaryWidth); + const double moduleExtraPhi = TMath::ASin(totalBoundaryWidth / (2.0 * richPars.rMin)); + const double vesselGapHalfPhi = TMath::ASin(vesselPhiGap / (2.0 * shieldRMin)); + const double quadrantSpanPhi = TMath::Pi() / 2.0 - 2.0 * vesselGapHalfPhi; + const int modulesPerQuadrant = richPars.nTiles / 4; + + // Remaining angular space between the last module of a quadrant and the following vessel gap. + const double endModuleExtraPhi = TMath::Pi() / 2.0 - moduleExtraPhi - static_cast(modulesPerQuadrant) * moduleDeltaPhi; + const double lateralStartWallSpanPhi = moduleExtraPhi - vesselGapHalfPhi; + const double lateralEndWallSpanPhi = endModuleExtraPhi - vesselGapHalfPhi; + + if (quadrantSpanPhi <= 0.0 || lateralStartWallSpanPhi <= 0.0 || lateralEndWallSpanPhi <= 0.0 || lateralStartWallSpanPhi + lateralEndWallSpanPhi >= quadrantSpanPhi) { + LOGP(fatal, "RICH invalid quadrant angular dimensions: vessel span={}, start wall span={}, end wall span={}", quadrantSpanPhi, lateralStartWallSpanPhi, lateralEndWallSpanPhi); + } + + const double radToDeg = 180.0 / TMath::Pi(); + const double quadrantSpanDeg = quadrantSpanPhi * radToDeg; + const double lateralStartWallSpanDeg = lateralStartWallSpanPhi * radToDeg; + const double lateralEndWallSpanDeg = lateralEndWallSpanPhi * radToDeg; + const double vesselGapHalfDeg = vesselGapHalfPhi * radToDeg; + const double innerGasRadius = shieldRMin + innerWallThickness; + const double outerGasRadius = shieldRMax - outerWallThickness; + + // Inner cylindrical shielding, divided into four sectors. + auto* innerWallQuadrantShape = new TGeoTubeSeg("RICH_SHIELD_INNER_WALL_QUADRANT_SHAPE", shieldRMin, innerGasRadius, shieldLengthZ / 2.0, 0.0, quadrantSpanDeg); + + // Outer cylindrical shielding, divided into four sectors. + auto* outerWallQuadrantShape = new TGeoTubeSeg("RICH_SHIELD_OUTER_WALL_QUADRANT_SHAPE", outerGasRadius, shieldRMax, shieldLengthZ / 2.0, 0.0, quadrantSpanDeg); + + // End caps divided into four sectors. + auto* endCapQuadrantShape = new TGeoTubeSeg("RICH_SHIELD_ENDCAP_QUADRANT_SHAPE", shieldRMin, shieldRMax, endCapThicknessZ / 2.0, 0.0, quadrantSpanDeg); + + // Lateral wall at the beginning of each quadrant. + auto* lateralStartWallShape = new TGeoTubeSeg("RICH_SHIELD_LATERAL_START_WALL_SHAPE", innerGasRadius, outerGasRadius, shieldLengthZ / 2.0, 0.0, lateralStartWallSpanDeg); + + // Lateral wall at the end of each quadrant. + auto* lateralEndWallShape = new TGeoTubeSeg("RICH_SHIELD_LATERAL_END_WALL_SHAPE", innerGasRadius, outerGasRadius, shieldLengthZ / 2.0, 0.0, lateralEndWallSpanDeg); + + auto* innerWallQuadrantVolume = new TGeoVolume("RICH_SHIELD_INNER_WALL_QUADRANT", innerWallQuadrantShape, medArmaGel); + auto* outerWallQuadrantVolume = new TGeoVolume("RICH_SHIELD_OUTER_WALL_QUADRANT", outerWallQuadrantShape, medArmaGel); + auto* endCapPlusQuadrantVolume = new TGeoVolume("RICH_SHIELD_ENDCAP_PLUS_QUADRANT", endCapQuadrantShape, medArmaGel); + auto* endCapMinusQuadrantVolume = new TGeoVolume("RICH_SHIELD_ENDCAP_MINUS_QUADRANT", endCapQuadrantShape, medArmaGel); + auto* lateralStartWallVolume = new TGeoVolume("RICH_SHIELD_LATERAL_START_WALL", lateralStartWallShape, medArmaGel); + auto* lateralEndWallVolume = new TGeoVolume("RICH_SHIELD_LATERAL_END_WALL", lateralEndWallShape, medArmaGel); + + innerWallQuadrantVolume->SetLineColor(kOrange - 8); // kGray + outerWallQuadrantVolume->SetLineColor(kOrange - 8); // kGray + endCapPlusQuadrantVolume->SetLineColor(kOrange - 8); // kGray + endCapMinusQuadrantVolume->SetLineColor(kOrange - 8); // kGray + lateralStartWallVolume->SetLineColor(kOrange - 8); // kGray + lateralEndWallVolume->SetLineColor(kOrange - 8); // kGray + + innerWallQuadrantVolume->SetTransparency(0); // 80 + outerWallQuadrantVolume->SetTransparency(0); // 80 + endCapPlusQuadrantVolume->SetTransparency(0); // 80 + endCapMinusQuadrantVolume->SetTransparency(0); // 80 + lateralStartWallVolume->SetTransparency(0); // 80 + lateralEndWallVolume->SetTransparency(0); // 80 + + const double endCapCenterZ = shieldLengthZ / 2.0 + endCapThicknessZ / 2.0; + + for (int quadrant = 0; quadrant < 4; quadrant++) { + + const double quadrantStartDeg = -45.0 + static_cast(quadrant) * 90.0 + vesselGapHalfDeg; + const double quadrantEndDeg = quadrantStartDeg + quadrantSpanDeg; + + auto makeRotation = [&](const char* prefix, double angleDeg) { + auto* rotation = new TGeoRotation(Form("%s_%d", prefix, quadrant)); + rotation->RotateZ(angleDeg); + return rotation; + }; + + // Inner cylindrical wall sector. + gasEnvelopeVolume->AddNode(innerWallQuadrantVolume, quadrant + 1, new TGeoCombiTrans(0.0, 0.0, 0.0, makeRotation("RICHInnerQuadrantRotation", quadrantStartDeg))); + // Outer cylindrical wall sector. + gasEnvelopeVolume->AddNode(outerWallQuadrantVolume, quadrant + 1, new TGeoCombiTrans(0.0, 0.0, 0.0, makeRotation("RICHOuterQuadrantRotation", quadrantStartDeg))); + // Positive-z end cap sector. + gasEnvelopeVolume->AddNode(endCapPlusQuadrantVolume, quadrant + 1, new TGeoCombiTrans(0.0, 0.0, endCapCenterZ, makeRotation("RICHEndCapPlusQuadrantRotation", quadrantStartDeg))); + // Negative-z end cap sector. + gasEnvelopeVolume->AddNode(endCapMinusQuadrantVolume, quadrant + 1, new TGeoCombiTrans(0.0, 0.0, -endCapCenterZ, makeRotation("RICHEndCapMinusQuadrantRotation", quadrantStartDeg))); + // Start-side lateral wall. + gasEnvelopeVolume->AddNode(lateralStartWallVolume, quadrant + 1, new TGeoCombiTrans(0.0, 0.0, 0.0, makeRotation("RICHLateralStartRotation", quadrantStartDeg))); + // End-side lateral wall. + gasEnvelopeVolume->AddNode(lateralEndWallVolume, quadrant + 1, new TGeoCombiTrans(0.0, 0.0, 0.0, makeRotation("RICHLateralEndRotation", quadrantEndDeg - lateralEndWallSpanDeg))); + } + + LOGP(info, "RICH quadrant geometry: module pitch={} deg, module boundary half-gap={} deg, vessel half-gap={} deg", moduleDeltaPhi * radToDeg, moduleExtraPhi * radToDeg, vesselGapHalfDeg); + } + + // ============================================================ modules for (int iRing{0}; iRing < richPars.nRings; ++iRing) { if (!richPars.oddGeom && iRing == (richPars.nRings / 2)) { continue; @@ -156,18 +669,18 @@ void Detector::createGeometry() richPars.rMin, richPars.rMax, richPars.radiatorThickness, - (float)mVTile1[iRing], - (float)mVTile2[iRing], - (float)mLAerogelZ[iRing], + (double)mVTile1[iRing], + (double)mVTile2[iRing], + (double)mLAerogelZ[iRing], richPars.detectorThickness, - (float)mVMirror1[iRing], - (float)mVMirror2[iRing], + (double)mVMirror1[iRing], + (double)mVMirror2[iRing], richPars.zBaseSize, - (float)mR0Radiator[iRing], - (float)mR0PhotoDet[iRing], - (float)mTRplusG[iRing], - (float)mThetaBi[iRing], - GeometryTGeo::getRICHVolPattern()}; + (double)mR0Radiator[iRing], + (double)mR0PhotoDet[iRing], + (double)mTRplusG[iRing], + (double)mThetaBi[iRing], + richGasMotherName}; // GeometryTGeo::getRICHVolPattern() } if (richPars.enableFWDRich) { @@ -233,7 +746,12 @@ void Detector::Reset() bool Detector::ProcessHits(FairVolume* vol) { // This method is called from the MC stepping - if (!(fMC->TrackCharge())) { + + constexpr int kOpticalPhotonPDG = 50000050; + const bool isOpticalPhoton = (fMC->TrackPid() == kOpticalPhotonPDG); + const bool isChargedParticle = (TMath::Abs(fMC->TrackCharge()) > 0.0); + // Reject neutral particles other than optical photons. + if (!isChargedParticle && !isOpticalPhoton) { return false; } @@ -242,6 +760,39 @@ bool Detector::ProcessHits(FairVolume* vol) // Is it needed to keep a track reference when the outer ITS volume is encountered? auto stack = (o2::data::Stack*)fMC->GetStack(); + + // Only the active silicon volumes are registered as sensitive in + // defineSensitiveVolumes(). The explicit volume-name check is kept + // as a safety guard in case additional sensitive volumes are added. + if (isOpticalPhoton) { + const char* currentVolumeName = fMC->CurrentVolName(); + const bool isActiveSiliconVolume = currentVolumeName && TString(currentVolumeName).BeginsWith(GeometryTGeo::getRICHSensorPattern()); + // Create only one hit when entering the active silicon. + if (!isActiveSiliconVolume || !fMC->IsTrackEntering()) { + return false; + } + TLorentzVector photonPosition; + TLorentzVector photonMomentum; + fMC->TrackPosition(photonPosition); + fMC->TrackMomentum(photonMomentum); + constexpr unsigned char photonStatus = Hit::kTrackEntering; + addHit( + stack->GetCurrentTrackNumber(), + lay, + photonPosition.Vect(), + photonPosition.Vect(), + photonMomentum.Vect(), + photonMomentum.E(), + photonPosition.T(), + 0.0, + photonStatus, + photonStatus); + + stack->addHit(GetDetId()); + + return true; + } + if (fMC->IsTrackExiting() && (lay == 0 || lay == mRings.size() - 1)) { // Keep the track refs for the innermost and outermost rings only o2::TrackReference tr(*fMC, GetDetId()); @@ -380,30 +931,121 @@ void Detector::prepareLayout() } // Dimensioning tiles - double percentage = 0.999; - for (int iRing = 0; iRing < richPars.nRings; iRing++) { - if (iRing == richPars.nRings / 2) { - mVMirror1[iRing] = percentage * 2.0 * richPars.rMax * TMath::Sin(TMath::Pi() / double(richPars.nTiles)); - mVMirror2[iRing] = percentage * 2.0 * richPars.rMax * TMath::Sin(TMath::Pi() / double(richPars.nTiles)); - mVTile1[iRing] = percentage * 2.0 * richPars.rMin * TMath::Sin(TMath::Pi() / double(richPars.nTiles)); - mVTile2[iRing] = percentage * 2.0 * richPars.rMin * TMath::Sin(TMath::Pi() / double(richPars.nTiles)); - } else if (iRing > richPars.nRings / 2) { - mVMirror1[iRing] = percentage * 2.0 * richPars.rMax * TMath::Sin(TMath::Pi() / double(richPars.nTiles)); - mVMirror2[iRing] = percentage * 2.0 * mMinRadialMirror[iRing] * TMath::Sin(TMath::Pi() / double(richPars.nTiles)); - mVTile1[iRing] = percentage * 2.0 * mMaxRadialRadiator[iRing] * TMath::Sin(TMath::Pi() / double(richPars.nTiles)); - mVTile2[iRing] = percentage * 2.0 * richPars.rMin * TMath::Sin(TMath::Pi() / double(richPars.nTiles)); - } else if (iRing < richPars.nRings / 2) { - mVMirror2[iRing] = percentage * 2.0 * richPars.rMax * TMath::Sin(TMath::Pi() / double(richPars.nTiles)); - mVMirror1[iRing] = percentage * 2.0 * mMinRadialMirror[iRing] * TMath::Sin(TMath::Pi() / double(richPars.nTiles)); - mVTile2[iRing] = percentage * 2.0 * mMaxRadialRadiator[iRing] * TMath::Sin(TMath::Pi() / double(richPars.nTiles)); - mVTile1[iRing] = percentage * 2.0 * richPars.rMin * TMath::Sin(TMath::Pi() / double(richPars.nTiles)); + if (!richPars.flagUseQuadrants) { + double percentage = 0.999; + for (int iRing = 0; iRing < richPars.nRings; iRing++) { + if (iRing == richPars.nRings / 2) { + mVMirror1[iRing] = percentage * 2.0 * richPars.rMax * TMath::Tan(TMath::Pi() / double(richPars.nTiles)); + mVMirror2[iRing] = percentage * 2.0 * richPars.rMax * TMath::Tan(TMath::Pi() / double(richPars.nTiles)); + mVTile1[iRing] = percentage * 2.0 * richPars.rMin * TMath::Tan(TMath::Pi() / double(richPars.nTiles)); + mVTile2[iRing] = percentage * 2.0 * richPars.rMin * TMath::Tan(TMath::Pi() / double(richPars.nTiles)); + } else if (iRing > richPars.nRings / 2) { + mVMirror1[iRing] = percentage * 2.0 * richPars.rMax * TMath::Tan(TMath::Pi() / double(richPars.nTiles)); + mVMirror2[iRing] = percentage * 2.0 * mMinRadialMirror[iRing] * TMath::Tan(TMath::Pi() / double(richPars.nTiles)); + mVTile1[iRing] = percentage * 2.0 * mMaxRadialRadiator[iRing] * TMath::Tan(TMath::Pi() / double(richPars.nTiles)); + mVTile2[iRing] = percentage * 2.0 * richPars.rMin * TMath::Tan(TMath::Pi() / double(richPars.nTiles)); + } else { + mVMirror2[iRing] = percentage * 2.0 * richPars.rMax * TMath::Tan(TMath::Pi() / double(richPars.nTiles)); + mVMirror1[iRing] = percentage * 2.0 * mMinRadialMirror[iRing] * TMath::Tan(TMath::Pi() / double(richPars.nTiles)); + mVTile2[iRing] = percentage * 2.0 * mMaxRadialRadiator[iRing] * TMath::Tan(TMath::Pi() / double(richPars.nTiles)); + mVTile1[iRing] = percentage * 2.0 * richPars.rMin * TMath::Tan(TMath::Pi() / double(richPars.nTiles)); + } + } + + } else { + + const double totalBoundaryWidth = 2.0 * richPars.vesselThicknessShieldingLateral + richPars.vesselPhiGap; + const double quadrantDeltaPhi = solveQuadrantDeltaPhi(richPars.nTiles, richPars.rMin, totalBoundaryWidth); + if (!(quadrantDeltaPhi > 0.0)) { + LOGP(fatal, "RICH could not solve the quadrant module angular pitch"); + } + const double halfWidthFactor = TMath::Tan(quadrantDeltaPhi / 2.0); + double percentage = 0.999; + for (int iRing = 0; iRing < richPars.nRings; iRing++) { + if (iRing == richPars.nRings / 2) { + mVMirror1[iRing] = percentage * 2.0 * richPars.rMax * halfWidthFactor; + mVMirror2[iRing] = percentage * 2.0 * richPars.rMax * halfWidthFactor; + mVTile1[iRing] = percentage * 2.0 * richPars.rMin * halfWidthFactor; + mVTile2[iRing] = percentage * 2.0 * richPars.rMin * halfWidthFactor; + } else if (iRing > richPars.nRings / 2) { + mVMirror1[iRing] = percentage * 2.0 * richPars.rMax * halfWidthFactor; + mVMirror2[iRing] = percentage * 2.0 * mMinRadialMirror[iRing] * halfWidthFactor; + mVTile1[iRing] = percentage * 2.0 * mMaxRadialRadiator[iRing] * halfWidthFactor; + mVTile2[iRing] = percentage * 2.0 * richPars.rMin * halfWidthFactor; + + } else { + mVMirror2[iRing] = percentage * 2.0 * richPars.rMax * halfWidthFactor; + mVMirror1[iRing] = percentage * 2.0 * mMinRadialMirror[iRing] * halfWidthFactor; + mVTile2[iRing] = percentage * 2.0 * mMaxRadialRadiator[iRing] * halfWidthFactor; + mVTile1[iRing] = percentage * 2.0 * richPars.rMin * halfWidthFactor; + } + } + } + + // ============================================================ + // Cylindrical aerogel geometry + // ============================================================ + // + // In this mode the photosensors remain projective, but all + // aerogel tiles: + // + // - have identical dimensions; + // - are parallel to the beam axis; + // - lie at the same cylindrical radius; + // - are uniformly distributed along Z. + // + if (richPars.useCylindricalAerogel) { + + // In the even geometry the central projective ring is skipped + // in createGeometry(), so the number of actual aerogel rows is + // nRings - 1. + const int nAerogelRows = richPars.oddGeom ? richPars.nRings : richPars.nRings - 1; + + if (nAerogelRows <= 0) { + LOGP(fatal, "Invalid number of cylindrical aerogel rows: {}", nAerogelRows); + } + + if (richPars.nTiles <= 0) { + LOGP(fatal, "Invalid number of aerogel tiles in phi: {}", richPars.nTiles); + } + + const double thetaRef = 2.0 * TMath::ATan(TMath::Exp(-richPars.cylindricalAerogelEtaRef)); + const double cylindricalAerogelTileSizeZ = (2.0 * richPars.rMin / TMath::Tan(thetaRef)) / static_cast(nAerogelRows); + // const double cylindricalAerogelTileSizeRPhi = 2.0 * richPars.rMin * TMath::Tan(TMath::Pi() / static_cast(richPars.nTiles)); + double cylindricalAerogelTileSizeRPhi = 0.0; + + if (!richPars.flagUseQuadrants) { + // Original uniform-phi geometry. + cylindricalAerogelTileSizeRPhi = 2.0 * richPars.rMin * TMath::Tan(TMath::Pi() / static_cast(richPars.nTiles)); + } else { + const double totalBoundaryWidth = 2.0 * richPars.vesselThicknessShieldingLateral + richPars.vesselPhiGap; + const double quadrantDeltaPhi = solveQuadrantDeltaPhi(richPars.nTiles, richPars.rMin, totalBoundaryWidth); + cylindricalAerogelTileSizeRPhi = 2.0 * richPars.rMin * TMath::Tan(quadrantDeltaPhi / 2.0); + } + + LOGP(info, "Cylindrical aerogel: rows={}, etaRef={}, tileSizeZ={} cm, tileSizeRPhi={} cm", nAerogelRows, richPars.cylindricalAerogelEtaRef, cylindricalAerogelTileSizeZ, cylindricalAerogelTileSizeRPhi); + + for (int iRing = 0; iRing < richPars.nRings; iRing++) { + mLAerogelZ[iRing] = cylindricalAerogelTileSizeZ; + + // Equal values make the TGeoArb8 a rectangle instead of the projective trapezoid. + mVTile1[iRing] = cylindricalAerogelTileSizeRPhi; + mVTile2[iRing] = cylindricalAerogelTileSizeRPhi; } } // Translation parameters for (size_t iRing{0}; iRing < richPars.nRings; ++iRing) { - mR0Radiator[iRing] = mR0Tilt[iRing] - (mTRplusG[iRing] - richPars.radiatorThickness / 2) * TMath::Cos(mThetaBi[iRing]); - mR0PhotoDet[iRing] = mR0Tilt[iRing] - (richPars.detectorThickness / 2) * TMath::Cos(mThetaBi[iRing]); + + if (richPars.useCylindricalAerogel) { + mR0Radiator[iRing] = richPars.rMin + richPars.radiatorThickness / 2.0; + } else { + // Original projective aerogel position. + mR0Radiator[iRing] = mR0Tilt[iRing] - (mTRplusG[iRing] - richPars.radiatorThickness / 2.0) * TMath::Cos(mThetaBi[iRing]); + } + + // Photosensors remain projective for both configurations. + mR0PhotoDet[iRing] = mR0Tilt[iRing] - richPars.detectorThickness / 2.0 * TMath::Cos(mThetaBi[iRing]); } // FWD and BWD RICH diff --git a/Detectors/Upgrades/ALICE3/RICH/simulation/src/RICHRing.cxx b/Detectors/Upgrades/ALICE3/RICH/simulation/src/RICHRing.cxx index 46e6ae515b390..45c5c52b27043 100644 --- a/Detectors/Upgrades/ALICE3/RICH/simulation/src/RICHRing.cxx +++ b/Detectors/Upgrades/ALICE3/RICH/simulation/src/RICHRing.cxx @@ -19,183 +19,594 @@ #include #include +#include +#include + namespace o2 { namespace rich { +namespace // quadrant operations +{ + +double quadrantDeltaPhiEquation(double x, int nTilesPhi, double rMin, double totalBoundaryWidth) +{ + const double argument = totalBoundaryWidth * TMath::Cos(x / 2.0) / (2.0 * rMin); + if (TMath::Abs(argument) >= 1.0) { + return std::numeric_limits::quiet_NaN(); + } + const double rhs = 2.0 * TMath::Pi() / static_cast(nTilesPhi) - (8.0 / static_cast(nTilesPhi)) * TMath::ASin(argument); + return rhs - x; +} + +double solveQuadrantDeltaPhi(int nTilesPhi, double rMin, double totalBoundaryWidth) +{ + double lower = 0.0; + double upper = 1.1 * 2.0 * TMath::Pi() / static_cast(nTilesPhi); + double fLower = quadrantDeltaPhiEquation(lower, nTilesPhi, rMin, totalBoundaryWidth); + double fUpper = quadrantDeltaPhiEquation(upper, nTilesPhi, rMin, totalBoundaryWidth); + if (!std::isfinite(fLower) || !std::isfinite(fUpper) || fLower * fUpper > 0.0) { + return -1.0; + } + constexpr double tolerance = 1.0e-12; + constexpr int maxIterations = 200; + for (int iteration = 0; iteration < maxIterations; iteration++) { + const double middle = 0.5 * (lower + upper); + const double fMiddle = quadrantDeltaPhiEquation(middle, nTilesPhi, rMin, totalBoundaryWidth); + if (!std::isfinite(fMiddle)) { + return -1.0; + } + if (TMath::Abs(fMiddle) < tolerance || 0.5 * (upper - lower) < tolerance) { + return middle; + } + if (fLower * fMiddle < 0.0) { + upper = middle; + fUpper = fMiddle; + } else { + lower = middle; + fLower = fMiddle; + } + } + return 0.5 * (lower + upper); +} + +double quadrantModulePhi(int moduleIndex, int nTilesPhi, double deltaPhi, double extraPhi) +{ + const int modulesPerQuadrant = nTilesPhi / 4; + const int quadrant = moduleIndex / modulesPerQuadrant; + return extraPhi + static_cast(moduleIndex) * deltaPhi - TMath::Pi() / 4.0 + deltaPhi / 2.0 + 2.0 * static_cast(quadrant) * extraPhi; +} + +} // namespace + Ring::Ring(int rPosId, int nTilesPhi, - float rMin, - float rMax, - float radThick, - float radYmin, - float radYmax, - float radZ, - float photThick, - float photYmin, - float photYmax, - float photZ, - float radRad0, - float photR0, - float aerDetDistance, - float thetaB, + double rMin, + double rMax, + double radThick, + double radYmin, + double radYmax, + double radZ, + double photThick, + double photYmin, + double photYmax, + double photZ, + double radRad0, + double photR0, + double aerDetDistance, + double thetaB, const std::string motherName) : mNTiles{nTilesPhi}, mPosId{rPosId}, mRadThickness{radThick} { TGeoManager* geoManager = gGeoManager; TGeoVolume* motherVolume = geoManager->GetVolume(motherName.c_str()); + + if (!motherVolume) { + LOGP(fatal, + "RICH: mother volume {} not found while creating ring {}", + motherName, + rPosId); + } + + const auto& richPars = RICHBaseParam::Instance(); + + const bool useCylindricalAerogel = richPars.useCylindricalAerogel; + TGeoMedium* medAerogel = gGeoManager->GetMedium("RCH_AEROGEL$"); if (!medAerogel) { LOGP(fatal, "RICH: Aerogel medium not found"); } + TGeoMedium* medSi = gGeoManager->GetMedium("RCH_SILICON$"); if (!medSi) { LOGP(fatal, "RICH: Silicon medium not found"); } + + TGeoMedium* medCO2 = gGeoManager->GetMedium("RCH_CO2$"); + if (!medCO2) { + LOGP(fatal, "RICH: CO2 medium not found"); + } + + TGeoMedium* medFR4 = gGeoManager->GetMedium("RCH_FR4$"); + if (!medFR4) { + LOGP(fatal, "RICH: FR4 medium not found"); + } + TGeoMedium* medAr = gGeoManager->GetMedium("RCH_ARGON$"); if (!medAr) { LOGP(fatal, "RICH: Argon medium not found"); } - std::vector radiatorTiles(nTilesPhi), photoTiles(nTilesPhi), argonSectors(nTilesPhi); + + TGeoMedium* medAl = gGeoManager->GetMedium("RCH_ALUMINUM$"); + if (!medAl) { + LOGP(fatal, "RICH: Aluminum medium not found"); + } + + TGeoMedium* medSiAbsorber = gGeoManager->GetMedium("RCH_SILICON_ABSORBER$"); + if (!medSiAbsorber) { + LOGP(fatal, "RICH: Passive silicon absorber medium not found"); + } + + TGeoMedium* medSilicone = gGeoManager->GetMedium("RCH_SILICONE$"); + if (!medSilicone) { + LOGP(fatal, "RICH: Silicone medium not found"); + } + + TGeoMedium* medHTCC = gGeoManager->GetMedium("RCH_HTCC$"); + if (!medHTCC) { + LOGP(fatal, "RICH: HTCC medium not found"); + } + + std::vector radiatorTiles(nTilesPhi), photoFrames(nTilesPhi), photoTiles(nTilesPhi), gasSectors(nTilesPhi); LOGP(info, "Creating ring: id: {} with {} tiles. ", rPosId, nTilesPhi); LOGP(info, "Rmin: {} Rmax: {} RadThick: {} RadYmin: {} RadYmax: {} RadZ: {} PhotThick: {} PhotYmin: {} PhotYmax: {} PhotZ: {}, zTransRad: {}, zTransPhot: {}, ThetaB: {}", rMin, rMax, radThick, radYmin, radYmax, radZ, photThick, photYmin, photYmax, photZ, radRad0, photR0, thetaB); - float deltaPhiDeg = 360.0 / nTilesPhi; // Transformation are constructed in degrees... - float thetaBDeg = thetaB * 180.0 / TMath::Pi(); - int radTileCount{0}, photTileCount{0}, argSectorsCount{0}; + // Use different phi depending on use of quadrants or not + const bool flagUseQuadrants = richPars.flagUseQuadrants; + if (flagUseQuadrants && (nTilesPhi <= 0 || nTilesPhi % 4 != 0)) { + LOGP(fatal, "RICH quadrant geometry requires nTilesPhi to be positive and divisible by four; received {}", nTilesPhi); + } + const double regularDeltaPhi = 2.0 * TMath::Pi() / static_cast(nTilesPhi); + double moduleDeltaPhi = regularDeltaPhi; + double quadrantExtraPhi = 0.0; + + if (flagUseQuadrants) { + const double totalBoundaryWidth = 2.0 * richPars.vesselThicknessShieldingLateral + richPars.vesselPhiGap; + if (totalBoundaryWidth >= 2.0 * rMin) { + LOGP(fatal, "RICH quadrant boundary width {} cm is incompatible with rMin={} cm", totalBoundaryWidth, rMin); + } + moduleDeltaPhi = solveQuadrantDeltaPhi(nTilesPhi, rMin, totalBoundaryWidth); + + quadrantExtraPhi = TMath::ASin(totalBoundaryWidth / (2.0 * rMin)); + + if (!(moduleDeltaPhi > 0.0)) { + LOGP(fatal, "RICH ring {} could not solve the quadrant angular pitch", rPosId); + } + } + + auto modulePhiRad = [&](int moduleIndex) { + if (!flagUseQuadrants) { + // Original placement exactly. + return static_cast(moduleIndex) * regularDeltaPhi; + } + return quadrantModulePhi(moduleIndex, nTilesPhi, moduleDeltaPhi, quadrantExtraPhi); + }; + + const double thetaBDeg = thetaB * 180.0 / TMath::Pi(); + + const double sipmActiveSizeZ = richPars.sipmActiveSizeZ; + // const double sipmActiveSizeRPhi = richPars.sipmActiveSizeRPhi; + // Select width depending on having quadrants or not (and wall thickness) + const double sipmActiveSizeRPhi = flagUseQuadrants ? richPars.quadrantModuleSizeRPhi : richPars.sipmActiveSizeRPhi; + + const double pcb1Thickness = richPars.pcb1Thickness; + const double coolingPlateThickness = richPars.coolingPlateThickness; + const double pcb2Thickness = richPars.pcb2Thickness; + const double pcb3Thickness = richPars.pcb3Thickness; + + const double gapSiPMToPCB1 = richPars.gapSiPMToPCB1; + const double gapPCB1ToCoolingPlate = richPars.gapPCB1ToCoolingPlate; + const double gapCoolingPlateToPCB2 = richPars.gapCoolingPlateToPCB2; + const double gapPCB2ToPCB3 = richPars.gapPCB2ToPCB3; + + const bool oddGeom = richPars.oddGeom; + const bool useRectangularModules = richPars.useRectangularModules; + + const int nRings = richPars.nRings; + + const double moduleClearanceZ = richPars.moduleClearanceZ; + const double moduleClearanceRPhi = richPars.moduleClearanceRPhi; + + const double siliconeLayerThickness = richPars.siliconeLayerThickness; + const double activeSiliconThickness = richPars.activeSiliconThickness; + const double passiveSiliconThickness = photThick - activeSiliconThickness; + + const double siliconFrontSurfaceOffset = -photThick / 2.0; + const double siliconeCenterOffset = siliconFrontSurfaceOffset - siliconeLayerThickness / 2.0; + const double activeSiliconCenterOffset = siliconFrontSurfaceOffset + activeSiliconThickness / 2.0; + const double passiveSiliconCenterOffset = siliconFrontSurfaceOffset + activeSiliconThickness + passiveSiliconThickness / 2.0; + + if (siliconeLayerThickness <= 0.0) { + LOGP(fatal, "RICH: siliconeLayerThickness must be positive"); + } + + if (activeSiliconThickness <= 0.0 || activeSiliconThickness >= photThick) { + LOGP(fatal, "RICH: activeSiliconThickness={} cm must be larger than zero and smaller than detectorThickness={} cm", activeSiliconThickness, photThick); + } + + if (passiveSiliconThickness <= 0.0) { + LOGP(fatal, "RICH: passive silicon thickness must be positive"); + } + + if (moduleClearanceZ < 0.0 || moduleClearanceRPhi < 0.0) { + LOGP(fatal, "RICH: module clearances cannot be negative"); + } + + if (photThick <= 0.0 || sipmActiveSizeZ <= 0.0 || sipmActiveSizeRPhi <= 0.0 || pcb1Thickness <= 0.0 || coolingPlateThickness <= 0.0 || pcb2Thickness <= 0.0 || pcb3Thickness <= 0.0) { + LOGP(fatal, "RICH: SiPM and readout-stack dimensions must be positive"); + } + + if (gapSiPMToPCB1 < 0.0 || gapPCB1ToCoolingPlate < 0.0 || gapCoolingPlateToPCB2 < 0.0 || gapPCB2ToPCB3 < 0.0) { + LOGP(fatal, "RICH: readout-stack gaps cannot be negative"); + } + + const double minimumFrameSizeRPhi = photYmin < photYmax ? photYmin : photYmax; + + if (sipmActiveSizeZ > photZ || sipmActiveSizeRPhi > minimumFrameSizeRPhi) { + LOGP(fatal, + "RICH: rectangular module {} x {} cm2 does not fit inside the trapezoidal sector {} x [{}, {}] cm2 for ring {}. " + "For quadrant mode reduce: quadrantModuleSizeRPhi.", + sipmActiveSizeZ, sipmActiveSizeRPhi, photZ, photYmin, photYmax, rPosId); + } + + // Number of actual aerogel rows. + const int nAerogelRows = oddGeom ? nRings : nRings - 1; + + // Convert the projective-ring ID into a contiguous aerogel-row index. + // Example for nRings=11 and even geometry: + // projective IDs: 0 1 2 3 4 [5 skipped] 6 7 8 9 10 + // aerogel index: 0 1 2 3 4 5 6 7 8 9 + int aerogelRowIndex = rPosId; + if (!oddGeom && rPosId > nRings / 2) { + --aerogelRowIndex; + } + + const double cylindricalAerogelCenterZ = -0.5 * static_cast(nAerogelRows) * radZ + 0.5 * radZ + static_cast(aerogelRowIndex) * radZ; + + int radTileCount{0}, photTileCount{0}; // argSectorsCount{0}; + + if (flagUseQuadrants) { + LOGP(info, "RICH ring {} quadrant placement: deltaPhi={} deg, boundary half-gap={} deg", rPosId, moduleDeltaPhi * 180.0 / TMath::Pi(), quadrantExtraPhi * 180.0 / TMath::Pi()); + } + // Radiator tiles for (auto& radiatorTile : radiatorTiles) { // Local Z is the thin (radial) dimension, looking outward from the IP // (previously this was local X, while for running with ACTS we need local Z). // The placement rotation below is adjusted by +90 deg about Y // to keep the tile in the same physical position. - radiatorTile = new TGeoArb8(radThick / 2); - radiatorTile->SetVertex(0, radZ / 2, -radYmin / 2); - radiatorTile->SetVertex(1, -radZ / 2, -radYmax / 2); - radiatorTile->SetVertex(2, -radZ / 2, radYmax / 2); - radiatorTile->SetVertex(3, radZ / 2, radYmin / 2); - radiatorTile->SetVertex(4, radZ / 2, -radYmin / 2); - radiatorTile->SetVertex(5, -radZ / 2, -radYmax / 2); - radiatorTile->SetVertex(6, -radZ / 2, radYmax / 2); - radiatorTile->SetVertex(7, radZ / 2, radYmin / 2); + if (useCylindricalAerogel) { + // Including gab between adjacent aerogel tiles + const double cylindricalTileSizeZ = radZ - moduleClearanceZ; + const double cylindricalTileYmin = radYmin - moduleClearanceRPhi; + const double cylindricalTileYmax = radYmax - moduleClearanceRPhi; + if (cylindricalTileSizeZ <= 0.0 || cylindricalTileYmin <= 0.0 || cylindricalTileYmax <= 0.0) { + LOGP(fatal, "RICH: cylindrical-aerogel clearances are larger than the tile dimensions for ring {}", rPosId); + } + radiatorTile = new TGeoArb8(radThick / 2); + radiatorTile->SetVertex(0, cylindricalTileSizeZ / 2, -cylindricalTileYmin / 2); + radiatorTile->SetVertex(1, -cylindricalTileSizeZ / 2, -cylindricalTileYmax / 2); + radiatorTile->SetVertex(2, -cylindricalTileSizeZ / 2, cylindricalTileYmax / 2); + radiatorTile->SetVertex(3, cylindricalTileSizeZ / 2, cylindricalTileYmin / 2); + radiatorTile->SetVertex(4, cylindricalTileSizeZ / 2, -cylindricalTileYmin / 2); + radiatorTile->SetVertex(5, -cylindricalTileSizeZ / 2, -cylindricalTileYmax / 2); + radiatorTile->SetVertex(6, -cylindricalTileSizeZ / 2, cylindricalTileYmax / 2); + radiatorTile->SetVertex(7, cylindricalTileSizeZ / 2, cylindricalTileYmin / 2); + } else { + // Original non-cylindrical tile definition. + radiatorTile = new TGeoArb8(radThick / 2); + radiatorTile->SetVertex(0, radZ / 2, -radYmin / 2); + radiatorTile->SetVertex(1, -radZ / 2, -radYmax / 2); + radiatorTile->SetVertex(2, -radZ / 2, radYmax / 2); + radiatorTile->SetVertex(3, radZ / 2, radYmin / 2); + radiatorTile->SetVertex(4, radZ / 2, -radYmin / 2); + radiatorTile->SetVertex(5, -radZ / 2, -radYmax / 2); + radiatorTile->SetVertex(6, -radZ / 2, radYmax / 2); + radiatorTile->SetVertex(7, radZ / 2, radYmin / 2); + } TGeoVolume* radiatorTileVol = new TGeoVolume(Form("radTile_%d_%d", rPosId, radTileCount), radiatorTile, medAerogel); - radiatorTileVol->SetLineColor(kOrange - 8); + radiatorTileVol->SetLineColor(kBlue - 9); radiatorTileVol->SetLineWidth(1); + // const double phiDeg = static_cast(radTileCount) * deltaPhiDeg; + // const double phiRad = static_cast(radTileCount) * 2.0 * TMath::Pi() / static_cast(nTilesPhi); + + const double phiRad = modulePhiRad(radTileCount); + const double phiDeg = phiRad * 180.0 / TMath::Pi(); + auto* rotRadiator = new TGeoRotation(Form("radTileRotation_%d_%d", radTileCount, rPosId)); - rotRadiator->RotateY(90.0 - thetaBDeg); // +90 compensates the X->Z swap of the tile's local axes - rotRadiator->RotateZ(radTileCount * deltaPhiDeg); - auto* rotTransRadiator = new TGeoCombiTrans(radRad0 * TMath::Cos(radTileCount * TMath::Pi() / (nTilesPhi / 2)), - radRad0 * TMath::Sin(radTileCount * TMath::Pi() / (nTilesPhi / 2)), - radRad0 * TMath::Tan(thetaB), - rotRadiator); + if (useCylindricalAerogel) { + // The TGeoArb8 local Z axis is the thin direction. + // RotateY(90 degrees) maps that thin local Z direction onto the global radial direction at phi=0. + // There is no thetaB tilt because the cylindrical aerogel tiles are parallel to the beam axis. + rotRadiator->RotateY(90.0); + } else { + // Original projective rotation. + rotRadiator->RotateY(90.0 - thetaBDeg); + } + + // Rotate the radial tile around the beam axis to its phi sector. + rotRadiator->RotateZ(phiDeg); + + const double radiatorCenterZ = useCylindricalAerogel ? cylindricalAerogelCenterZ : radRad0 * TMath::Tan(thetaB); + + auto* rotTransRadiator = new TGeoCombiTrans(radRad0 * TMath::Cos(phiRad), radRad0 * TMath::Sin(phiRad), radiatorCenterZ, rotRadiator); motherVolume->AddNode(radiatorTileVol, 1, rotTransRadiator); radTileCount++; } - // Photosensor tiles - for (auto& photoTile : photoTiles) { - // Local Z is the thin (radial) dimension, looking outward from the IP - photoTile = new TGeoArb8(photThick / 2); - photoTile->SetVertex(0, photZ / 2, -photYmin / 2); - photoTile->SetVertex(1, -photZ / 2, -photYmax / 2); - photoTile->SetVertex(2, -photZ / 2, photYmax / 2); - photoTile->SetVertex(3, photZ / 2, photYmin / 2); - photoTile->SetVertex(4, photZ / 2, -photYmin / 2); - photoTile->SetVertex(5, -photZ / 2, -photYmax / 2); - photoTile->SetVertex(6, -photZ / 2, photYmax / 2); - photoTile->SetVertex(7, photZ / 2, photYmin / 2); - - TGeoVolume* photoTileVol = new TGeoVolume(Form("%s_%d_%d", GeometryTGeo::getRICHSensorPattern(), rPosId, photTileCount), photoTile, medSi); - photoTileVol->SetLineColor(kOrange - 8); - photoTileVol->SetLineWidth(1); - - auto* rotPhoto = new TGeoRotation(Form("photoTileRotation_%d_%d", photTileCount, rPosId)); - rotPhoto->RotateY(90.0 - thetaBDeg); // +90 compensates the X->Z swap of the tile's local axes - rotPhoto->RotateZ(photTileCount * deltaPhiDeg); - auto* rotTransPhoto = new TGeoCombiTrans(photR0 * TMath::Cos(photTileCount * TMath::Pi() / (nTilesPhi / 2)), - photR0 * TMath::Sin(photTileCount * TMath::Pi() / (nTilesPhi / 2)), - photR0 * TMath::Tan(thetaB), - rotPhoto); - - motherVolume->AddNode(photoTileVol, 1, rotTransPhoto); - photTileCount++; - } - - // Argon sectors "connect" radiator and photosensor tiles, they are not really physical - for (auto& argonSector : argonSectors) { - float separation{(aerDetDistance - radThick - photThick)}; + // Photosensor tiles: legacy trapezoidal modules and rectangular modules + if (!useRectangularModules) { + for (auto& photoTile : photoTiles) { + const double phiRad = modulePhiRad(photTileCount); + const double phiDeg = phiRad * 180.0 / TMath::Pi(); + // Local Z is the thin (radial) dimension, looking outward from the IP + photoTile = new TGeoArb8(photThick / 2); + photoTile->SetVertex(0, photZ / 2, -photYmin / 2); + photoTile->SetVertex(1, -photZ / 2, -photYmax / 2); + photoTile->SetVertex(2, -photZ / 2, photYmax / 2); + photoTile->SetVertex(3, photZ / 2, photYmin / 2); + photoTile->SetVertex(4, photZ / 2, -photYmin / 2); + photoTile->SetVertex(5, -photZ / 2, -photYmax / 2); + photoTile->SetVertex(6, -photZ / 2, photYmax / 2); + photoTile->SetVertex(7, photZ / 2, photYmin / 2); + + TGeoVolume* photoTileVol = new TGeoVolume(Form("%s_%d_%d", GeometryTGeo::getRICHSensorPattern(), rPosId, photTileCount), photoTile, medSi); + photoTileVol->SetLineColor(kOrange + 2); + photoTileVol->SetLineWidth(1); + + auto* rotPhoto = new TGeoRotation(Form("photoTileRotation_%d_%d", photTileCount, rPosId)); + rotPhoto->RotateY(90.0 - thetaBDeg); // +90 compensates the X->Z swap of the tile's local axes + // rotPhoto->RotateZ(photTileCount * deltaPhiDeg); + rotPhoto->RotateZ(phiDeg); + // auto* rotTransPhoto = new TGeoCombiTrans(photR0 * TMath::Cos(photTileCount * TMath::Pi() / (nTilesPhi / 2)), + // photR0 * TMath::Sin(photTileCount * TMath::Pi() / (nTilesPhi / 2)), + // photR0 * TMath::Tan(thetaB), + // rotPhoto); + auto* rotTransPhoto = new TGeoCombiTrans(photR0 * TMath::Cos(phiRad), photR0 * TMath::Sin(phiRad), photR0 * TMath::Tan(thetaB), rotPhoto); + + motherVolume->AddNode(photoTileVol, 1, rotTransPhoto); + photTileCount++; + } + } else // <-- New gemetry with rectangular modules + { + // Photosensor tiles and readout stack + for (auto& photoTile : photoTiles) { + // const double phiDeg = static_cast(photTileCount) * deltaPhiDeg; + // const double phiRad = static_cast(photTileCount) * 2.0 * TMath::Pi() / static_cast(nTilesPhi); + const double phiRad = modulePhiRad(photTileCount); + const double phiDeg = phiRad * 180.0 / TMath::Pi(); + + const double photoCenterR = photR0; + const double photoCenterZ = photR0 * TMath::Tan(thetaB); + + // Unit vector normal to the projective plane, pointing away from the IP. Positive offset places layer behind the SiPM. + const double normalRadial = TMath::Cos(thetaB); + const double normalZ = TMath::Sin(thetaB); + + auto makeProjectiveRotation = [&](const char* prefix) { + auto* rotation = new TGeoRotation(Form("%sRotation_%d_%d", prefix, photTileCount, rPosId)); + rotation->RotateY(90.0 - thetaBDeg); // same orientation as the original photosensor + rotation->RotateZ(phiDeg); + return rotation; + }; + + const double frameSizeZ = photZ - moduleClearanceZ; + const double frameYmin = photYmin - moduleClearanceRPhi; + const double frameYmax = photYmax - moduleClearanceRPhi; + // Footprint of the frames with the configured clearances for overlaps + auto makeFrameFootprint = [&](double thickness) { + auto* shape = new TGeoArb8(thickness / 2.0); + shape->SetVertex(0, frameSizeZ / 2.0, -frameYmin / 2.0); + shape->SetVertex(1, -frameSizeZ / 2.0, -frameYmax / 2.0); + shape->SetVertex(2, -frameSizeZ / 2.0, frameYmax / 2.0); + shape->SetVertex(3, frameSizeZ / 2.0, frameYmin / 2.0); + shape->SetVertex(4, frameSizeZ / 2.0, -frameYmin / 2.0); + shape->SetVertex(5, -frameSizeZ / 2.0, -frameYmax / 2.0); + shape->SetVertex(6, -frameSizeZ / 2.0, frameYmax / 2.0); + shape->SetVertex(7, frameSizeZ / 2.0, frameYmin / 2.0); + return shape; + }; + + auto makeRectangularFootprint = [&](double thickness) { + auto* shape = new TGeoArb8(thickness / 2.0); + shape->SetVertex(0, sipmActiveSizeZ / 2.0, -sipmActiveSizeRPhi / 2.0); + shape->SetVertex(1, -sipmActiveSizeZ / 2.0, -sipmActiveSizeRPhi / 2.0); + shape->SetVertex(2, -sipmActiveSizeZ / 2.0, sipmActiveSizeRPhi / 2.0); + shape->SetVertex(3, sipmActiveSizeZ / 2.0, sipmActiveSizeRPhi / 2.0); + shape->SetVertex(4, sipmActiveSizeZ / 2.0, -sipmActiveSizeRPhi / 2.0); + shape->SetVertex(5, -sipmActiveSizeZ / 2.0, -sipmActiveSizeRPhi / 2.0); + shape->SetVertex(6, -sipmActiveSizeZ / 2.0, sipmActiveSizeRPhi / 2.0); + shape->SetVertex(7, sipmActiveSizeZ / 2.0, sipmActiveSizeRPhi / 2.0); + return shape; + }; + + auto addReadoutLayer = [&](const char* prefix, + double thickness, + double centerOffset, + TGeoMedium* medium, + Color_t lineColor, + bool useRectangularFootprint) { + auto* shape = useRectangularFootprint ? makeRectangularFootprint(thickness) : makeFrameFootprint(thickness); + auto* volume = new TGeoVolume(Form("%s_%d_%d", prefix, rPosId, photTileCount), shape, medium); + volume->SetLineColor(lineColor); + volume->SetLineWidth(1); + const double layerCenterR = photoCenterR + centerOffset * normalRadial; + const double layerCenterZ = photoCenterZ + centerOffset * normalZ; + auto* transform = new TGeoCombiTrans(layerCenterR * TMath::Cos(phiRad), layerCenterR * TMath::Sin(phiRad), layerCenterZ, makeProjectiveRotation(prefix)); + motherVolume->AddNode(volume, 1, transform); + }; + + // ------------------------------------------------------------ + // Optional trapezoidal frame + // ------------------------------------------------------------ + // This is exactly the old photosensor envelope. It is created for + // reference, but deliberately not added to the geometry. + photoFrames[photTileCount] = makeFrameFootprint(photThick); + auto* photoFrameVol = new TGeoVolume(Form("photoFrame_%d_%d", rPosId, photTileCount), photoFrames[photTileCount], medSi); + photoFrameVol->SetLineColor(kGray + 2); + photoFrameVol->SetLineWidth(1); + // Uncomment only when the mechanical frame material/solid geometry should be included. + // This would be a solid trapezoid and would overlap the sensitive silicon: need for opening + // motherVolume->AddNode(photoFrameVol, 1, new TGeoCombiTrans(photoCenterR * TMath::Cos(phiRad), photoCenterR * TMath::Sin(phiRad), photoCenterZ, makeProjectiveRotation("photoFrame"))); + + // ------------------------------------------------------------ + // True sensitive silicon: centered 17 x 18 cm2 rectangle + // ------------------------------------------------------------ + // Local X corresponds to the in-plane Z direction after placement. + // Local Y corresponds to the in-plane r-phi direction. + // Local Z is the 1 mm thickness direction. + /*photoTile = new TGeoArb8(photThick / 2.0); + photoTile->SetVertex(0, sipmActiveSizeZ / 2.0, -sipmActiveSizeRPhi / 2.0); + photoTile->SetVertex(1, -sipmActiveSizeZ / 2.0, -sipmActiveSizeRPhi / 2.0); + photoTile->SetVertex(2, -sipmActiveSizeZ / 2.0, sipmActiveSizeRPhi / 2.0); + photoTile->SetVertex(3, sipmActiveSizeZ / 2.0, sipmActiveSizeRPhi / 2.0); + photoTile->SetVertex(4, sipmActiveSizeZ / 2.0, -sipmActiveSizeRPhi / 2.0); + photoTile->SetVertex(5, -sipmActiveSizeZ / 2.0, -sipmActiveSizeRPhi / 2.0); + photoTile->SetVertex(6, -sipmActiveSizeZ / 2.0, sipmActiveSizeRPhi / 2.0); + photoTile->SetVertex(7, sipmActiveSizeZ / 2.0, sipmActiveSizeRPhi / 2.0); + auto* photoTileVol = new TGeoVolume(Form("%s_%d_%d", GeometryTGeo::getRICHSensorPattern(), rPosId, photTileCount), photoTile, medSi); + photoTileVol->SetLineColor(kRed); + photoTileVol->SetLineWidth(1); + auto* rotTransPhoto = new TGeoCombiTrans(photoCenterR * TMath::Cos(phiRad), photoCenterR * TMath::Sin(phiRad), photoCenterZ, makeProjectiveRotation("photoTile")); + motherVolume->AddNode(photoTileVol, 1, rotTransPhoto);*/ + + // Silicone resin in front of SiPMs + addReadoutLayer("siliconeLayer", siliconeLayerThickness, siliconeCenterOffset, medSilicone, kOrange + 2, useRectangularModules); + + // Active sensitive silicon. + photoTile = makeRectangularFootprint(activeSiliconThickness); + auto* photoTileVol = new TGeoVolume(Form("%s_%d_%d", GeometryTGeo::getRICHSensorPattern(), rPosId, photTileCount), photoTile, medSi); + const double activeSiliconCenterR = photoCenterR + activeSiliconCenterOffset * normalRadial; + const double activeSiliconCenterZ = photoCenterZ + activeSiliconCenterOffset * normalZ; + auto* rotTransPhoto = new TGeoCombiTrans(activeSiliconCenterR * TMath::Cos(phiRad), activeSiliconCenterR * TMath::Sin(phiRad), activeSiliconCenterZ, makeProjectiveRotation("photoTile")); + motherVolume->AddNode(photoTileVol, 1, rotTransPhoto); + + // Passive silicon absorber. + addReadoutLayer("siliconAbsorber", passiveSiliconThickness, passiveSiliconCenterOffset, medSiAbsorber, kBlue + 1, true); + + // ------------------------------------------------------------ + // Stack behind the SiPM + // ------------------------------------------------------------ + // Every gap is surface-to-surface. centerOffset is measured from + // the SiPM center along the outward local normal. + double outerSurfaceOffset = photThick / 2.0; + + outerSurfaceOffset += gapSiPMToPCB1; + const double pcb1CenterOffset = outerSurfaceOffset + pcb1Thickness / 2.0; + addReadoutLayer("pcb1", pcb1Thickness, pcb1CenterOffset, medFR4, kGreen + 1, useRectangularModules); + outerSurfaceOffset += pcb1Thickness; + + outerSurfaceOffset += gapPCB1ToCoolingPlate; + const double coolingPlateCenterOffset = outerSurfaceOffset + coolingPlateThickness / 2.0; + addReadoutLayer("coolingPlate", coolingPlateThickness, coolingPlateCenterOffset, medHTCC, kRed, useRectangularModules); + outerSurfaceOffset += coolingPlateThickness; + + outerSurfaceOffset += gapCoolingPlateToPCB2; + const double pcb2CenterOffset = outerSurfaceOffset + pcb2Thickness / 2.0; + addReadoutLayer("pcb2", pcb2Thickness, pcb2CenterOffset, medFR4, kGreen + 2, useRectangularModules); + outerSurfaceOffset += pcb2Thickness; + + outerSurfaceOffset += gapPCB2ToPCB3; + const double pcb3CenterOffset = outerSurfaceOffset + pcb3Thickness / 2.0; + addReadoutLayer("pcb3", pcb3Thickness, pcb3CenterOffset, medFR4, kGreen + 3, useRectangularModules); + + photTileCount++; + } + } + + // Gas sectors (argon) - legacy code, not used in the current geometry, but kept for reference + /* + for (auto& gasSector : gasSectors) { + double separation{(aerDetDistance - radThick - photThick)}; auto* radiator = radiatorTiles[argSectorsCount]; auto* photosensor = photoTiles[argSectorsCount]; - argonSector = new TGeoArb8(separation / 2); - - argonSector->SetVertex(0, -photZ / 2, -photYmin / 2); - argonSector->SetVertex(1, -photZ / 2, photYmin / 2); - argonSector->SetVertex(2, photZ / 2, photYmax / 2); - argonSector->SetVertex(3, photZ / 2, -photYmax / 2); - argonSector->SetVertex(4, -radZ / 2, -radYmin / 2); - argonSector->SetVertex(5, -radZ / 2, radYmin / 2); - argonSector->SetVertex(6, radZ / 2, radYmax / 2); - argonSector->SetVertex(7, radZ / 2, -radYmax / 2); - - TGeoVolume* argonSectorVol = new TGeoVolume(Form("argonSector_%d_%d", rPosId, argSectorsCount), argonSector, medAr); - argonSectorVol->SetVisibility(kTRUE); - argonSectorVol->SetLineColor(kOrange - 8); - argonSectorVol->SetLineWidth(1); - auto* rotArgon = new TGeoRotation(Form("argonSectorRotation_%d_%d", argSectorsCount, rPosId)); - rotArgon->RotateY(-90 - thetaBDeg); - rotArgon->RotateZ(argSectorsCount * deltaPhiDeg); - auto* rotTransArgon = new TGeoCombiTrans((radRad0 + TMath::Cos(thetaB) * (separation + radThick) / 2) * TMath::Cos(argSectorsCount * TMath::Pi() / (nTilesPhi / 2)), - (radRad0 + TMath::Cos(thetaB) * (separation + radThick) / 2) * TMath::Sin(argSectorsCount * TMath::Pi() / (nTilesPhi / 2)), - radRad0 * TMath::Tan(thetaB) + TMath::Sin(thetaB) * (separation + radThick) / 2, - rotArgon); - motherVolume->AddNode(argonSectorVol, 1, rotTransArgon); + gasSector = new TGeoArb8(separation / 2); + + gasSector->SetVertex(0, -photZ / 2, -photYmin / 2); + gasSector->SetVertex(1, -photZ / 2, photYmin / 2); + gasSector->SetVertex(2, photZ / 2, photYmax / 2); + gasSector->SetVertex(3, photZ / 2, -photYmax / 2); + gasSector->SetVertex(4, -radZ / 2, -radYmin / 2); + gasSector->SetVertex(5, -radZ / 2, radYmin / 2); + gasSector->SetVertex(6, radZ / 2, radYmax / 2); + gasSector->SetVertex(7, radZ / 2, -radYmax / 2); + + TGeoVolume* gasSectorVol = new TGeoVolume(Form("gasSector_%d_%d", rPosId, argSectorsCount), gasSector, medCO2); + gasSectorVol->SetVisibility(kTRUE); + gasSectorVol->SetLineColor(kOrange - 8); + gasSectorVol->SetLineWidth(1); + auto* rotGas = new TGeoRotation(Form("gasSectorRotation_%d_%d", argSectorsCount, rPosId)); + rotGas->RotateY(-90 - thetaBDeg); + //rotGas->RotateZ(argSectorsCount * deltaPhiDeg); + //auto* rotTransGas = new TGeoCombiTrans((radRad0 + TMath::Cos(thetaB) * (separation + radThick) / 2) * TMath::Cos(argSectorsCount * TMath::Pi() / (nTilesPhi / 2)), + // (radRad0 + TMath::Cos(thetaB) * (separation + radThick) / 2) * TMath::Sin(argSectorsCount * TMath::Pi() / (nTilesPhi / 2)), + // radRad0 * TMath::Tan(thetaB) + TMath::Sin(thetaB) * (separation + radThick) / 2, + // rotGas); + const double gasPhiRad = modulePhiRad(argSectorsCount); + rotGas->RotateZ(gasPhiRad * 180.0 / TMath::Pi()); + auto* rotTransGas = new TGeoCombiTrans((radRad0 + TMath::Cos(thetaB) * (separation + radThick) / 2.0) * TMath::Cos(gasPhiRad), + (radRad0 + TMath::Cos(thetaB) * (separation + radThick) / 2.0) * TMath::Sin(gasPhiRad), + radRad0 * TMath::Tan(thetaB) + TMath::Sin(thetaB) * (separation + radThick) / 2.0, + rotGas); + motherVolume->AddNode(gasSectorVol, 1, rotTransGas); argSectorsCount++; } + */ } FWDRich::FWDRich(std::string name, - float rMin, - float rMax, - float zAerogelMin, - float dZAerogel, - float zArgonMin, - float dZArgon, - float zSiliconMin, - float dZSilicon) : mName{name}, - mRmin{rMin}, - mRmax{rMax}, - mZAerogelMin{zAerogelMin}, - mDZAerogel{dZAerogel}, - mZArgonMin{zArgonMin}, - mDZArgon{dZArgon}, - mZSiliconMin{zSiliconMin}, - mDZSilicon{dZSilicon} + double rMin, + double rMax, + double zAerogelMin, + double dZAerogel, + double zArgonMin, + double dZArgon, + double zSiliconMin, + double dZSilicon) : mName{name}, + mRmin{rMin}, + mRmax{rMax}, + mZAerogelMin{zAerogelMin}, + mDZAerogel{dZAerogel}, + mZArgonMin{zArgonMin}, + mDZArgon{dZArgon}, + mZSiliconMin{zSiliconMin}, + mDZSilicon{dZSilicon} { } BWDRich::BWDRich(std::string name, - float rMin, - float rMax, - float zAerogelMin, - float dZAerogel, - float zArgonMin, - float dZArgon, - float zSiliconMin, - float dZSilicon) : mName{name}, - mRmin{rMin}, - mRmax{rMax}, - mZAerogelMin{zAerogelMin}, - mDZAerogel{dZAerogel}, - mZArgonMin{zArgonMin}, - mDZArgon{dZArgon}, - mZSiliconMin{zSiliconMin}, - mDZSilicon{dZSilicon} + double rMin, + double rMax, + double zAerogelMin, + double dZAerogel, + double zArgonMin, + double dZArgon, + double zSiliconMin, + double dZSilicon) : mName{name}, + mRmin{rMin}, + mRmax{rMax}, + mZAerogelMin{zAerogelMin}, + mDZAerogel{dZAerogel}, + mZArgonMin{zArgonMin}, + mDZArgon{dZArgon}, + mZSiliconMin{zSiliconMin}, + mDZSilicon{dZSilicon} { } diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/ChipDigitsContainer.h b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/ChipDigitsContainer.h deleted file mode 100644 index bf28ace0724bc..0000000000000 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/ChipDigitsContainer.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. -// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. -// All rights not expressly granted are reserved. -// -// This software is distributed under the terms of the GNU General Public -// License v3 (GPL Version 3), copied verbatim in the file "COPYING". -// -// In applying this license CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -#ifndef ALICEO2_TRK_CHIPDIGITSCONTAINER_ -#define ALICEO2_TRK_CHIPDIGITSCONTAINER_ - -#include "ITSMFTBase/SegmentationAlpide.h" -#include "ITSMFTSimulation/ChipDigitsContainer.h" -#include "TRKBase/SegmentationChip.h" -#include "TRKBase/Specs.h" -#include "TRKSimulation/DigiParams.h" -#include - -namespace o2::trk -{ - -class ChipDigitsContainer : public o2::itsmft::ChipDigitsContainer -{ - public: - explicit ChipDigitsContainer(UShort_t idx = 0); - - using Segmentation = SegmentationChip; - - /// Get global ordering key made of readout frame, column and row - static ULong64_t getOrderingKey(UInt_t roframe, UShort_t row, UShort_t col) - { - return (static_cast(roframe) << (8 * sizeof(UInt_t))) + (static_cast(col) << (8 * sizeof(Short_t))) + row; - } - - /// Adds noise digits, deleted the one using the itsmft::DigiParams interface - void addNoise(UInt_t rofMin, UInt_t rofMax, const o2::itsmft::DigiParams* params, int maxRows = o2::itsmft::SegmentationAlpide::NRows, int maxCols = o2::itsmft::SegmentationAlpide::NCols) = delete; - void addNoise(UInt_t rofMin, UInt_t rofMax, const o2::trk::DigiParams* params, int subDetID, int layer); - - ClassDefNV(ChipDigitsContainer, 1); -}; - -} // namespace o2::trk - -#endif // ALICEO2_TRK_CHIPDIGITSCONTAINER_ diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/ChipDigitsContainer.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/ChipDigitsContainer.cxx deleted file mode 100644 index d8e6df8b6099c..0000000000000 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/ChipDigitsContainer.cxx +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. -// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. -// All rights not expressly granted are reserved. -// -// This software is distributed under the terms of the GNU General Public -// License v3 (GPL Version 3), copied verbatim in the file "COPYING". -// -// In applying this license CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -#include "TRKSimulation/ChipDigitsContainer.h" - -using namespace o2::trk; - -ChipDigitsContainer::ChipDigitsContainer(UShort_t idx) - : o2::itsmft::ChipDigitsContainer(idx) {} - -//______________________________________________________________________ -void ChipDigitsContainer::addNoise(UInt_t rofMin, UInt_t rofMax, const o2::trk::DigiParams* params, int subDetID, int layer) -{ - UInt_t row = 0; - UInt_t col = 0; - Int_t nhits = 0; - constexpr float ns2sec = 1e-9; - float mean = 0.f; - int nel = 0; - int maxRows = 0; - int maxCols = 0; - - // TODO: set different noise and threshold for VD and MLOT - if (subDetID == 0) { // VD - maxRows = constants::VD::petal::layer::nRows[layer]; // TODO: get the layer from the geometry - maxCols = constants::VD::petal::layer::nCols; - mean = params->getNoisePerPixel() * maxRows * maxCols; - nel = static_cast(params->getChargeThreshold() * 1.1); - } else { // ML/OT - maxRows = constants::moduleMLOT::chip::nRows; - maxCols = constants::moduleMLOT::chip::nCols; - mean = params->getNoisePerPixel() * maxRows * maxCols; - nel = static_cast(params->getChargeThreshold() * 1.1); - } - - LOG(debug) << "Adding noise for chip " << mChipIndex << " with mean " << mean << " and charge " << nel; - - for (UInt_t rof = rofMin; rof <= rofMax; rof++) { - nhits = gRandom->Poisson(mean); - for (Int_t i = 0; i < nhits; ++i) { - row = gRandom->Integer(maxRows); - col = gRandom->Integer(maxCols); - LOG(debug) << "Generated noise hit at ROF " << rof << ", row " << row << ", col " << col; - if (mNoiseMap && mNoiseMap->isNoisy(mChipIndex, row, col)) { - continue; - } - if (mDeadChanMap && mDeadChanMap->isNoisy(mChipIndex, row, col)) { - continue; - } - auto key = getOrderingKey(rof, row, col); - if (!findDigit(key)) { - addDigit(key, rof, row, col, nel, o2::MCCompLabel(true)); - } - } - } -} diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/Hit.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/Hit.cxx deleted file mode 100644 index 1f49b84114b9d..0000000000000 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/Hit.cxx +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. -// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. -// All rights not expressly granted are reserved. -// -// This software is distributed under the terms of the GNU General Public -// License v3 (GPL Version 3), copied verbatim in the file "COPYING". -// -// In applying this license CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -/// \file Hit.cxx -/// \brief Implementation of the Hit class - -#include "TRKSimulation/Hit.h" diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRKFT3/CMakeLists.txt new file mode 100644 index 0000000000000..3f9a281e64480 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRKFT3/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright 2019-2020 CERN and copyright holders of ALICE O2. +# See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +# All rights not expressly granted are reserved. +# +# This software is distributed under the terms of the GNU General Public +# License v3 (GPL Version 3), copied verbatim in the file "COPYING". +# +# In applying this license CERN does not waive the privileges and immunities +# granted to it by virtue of its status as an Intergovernmental Organization +# or submit itself to any jurisdiction. + +add_subdirectory(FT3/base) +add_subdirectory(TRK/base) +add_subdirectory(common) +add_subdirectory(FT3/simulation) +add_subdirectory(TRK/macros) +add_subdirectory(TRK/simulation) diff --git a/Detectors/Upgrades/ALICE3/FT3/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/CMakeLists.txt similarity index 100% rename from Detectors/Upgrades/ALICE3/FT3/CMakeLists.txt rename to Detectors/Upgrades/ALICE3/TRKFT3/FT3/CMakeLists.txt index 2d9ff8a9ac78e..3dde618f9d57a 100644 --- a/Detectors/Upgrades/ALICE3/FT3/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/CMakeLists.txt @@ -9,5 +9,5 @@ # granted to it by virtue of its status as an Intergovernmental Organization # or submit itself to any jurisdiction. -add_subdirectory(simulation) add_subdirectory(base) +add_subdirectory(simulation) diff --git a/Detectors/Upgrades/ALICE3/FT3/README.md b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/README.md similarity index 100% rename from Detectors/Upgrades/ALICE3/FT3/README.md rename to Detectors/Upgrades/ALICE3/TRKFT3/FT3/README.md diff --git a/Detectors/Upgrades/ALICE3/FT3/base/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/CMakeLists.txt similarity index 91% rename from Detectors/Upgrades/ALICE3/FT3/base/CMakeLists.txt rename to Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/CMakeLists.txt index 3e4925e78bd11..1cfb57c4beb84 100644 --- a/Detectors/Upgrades/ALICE3/FT3/base/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/CMakeLists.txt @@ -12,7 +12,7 @@ o2_add_library(FT3Base SOURCES src/GeometryTGeo.cxx SOURCES src/FT3BaseParam.cxx - PUBLIC_LINK_LIBRARIES O2::DetectorsBase O2::ITSMFTBase) + PUBLIC_LINK_LIBRARIES O2::DetectorsBase) o2_target_root_dictionary(FT3Base HEADERS include/FT3Base/GeometryTGeo.h diff --git a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/include/FT3Base/FT3BaseParam.h similarity index 97% rename from Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h rename to Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/include/FT3Base/FT3BaseParam.h index d7156b5c92582..f4619c608c099 100644 --- a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/include/FT3Base/FT3BaseParam.h @@ -29,7 +29,7 @@ enum eFT3Layout { }; struct FT3BaseParam : public o2::conf::ConfigurableParamHelper { // Geometry Builder parameters - eFT3Layout layoutFT3 = kSegmentedStaveOTOnly; + eFT3Layout layoutFT3 = kSegmentedStave; int nTrapezoidalSegments = 32; // for the simple trapezoidal disks // FT3Geometry::Telescope parameters diff --git a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/GeometryTGeo.h b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/include/FT3Base/GeometryTGeo.h similarity index 54% rename from Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/GeometryTGeo.h rename to Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/include/FT3Base/GeometryTGeo.h index 1941b543579db..2415da33c976e 100644 --- a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/GeometryTGeo.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/include/FT3Base/GeometryTGeo.h @@ -18,39 +18,30 @@ #ifndef ALICEO2_FT3_GEOMETRYTGEO_H_ #define ALICEO2_FT3_GEOMETRYTGEO_H_ -#include // for TGeoHMatrix -#include // for TObject -#include -#include -#include -#include "DetectorsBase/GeometryManager.h" +#include +#include "DetectorsCommonDataFormats/DetMatrixCache.h" #include "DetectorsCommonDataFormats/DetID.h" -#include "ITSMFTBase/GeometryTGeo.h" -#include "MathUtils/Utils.h" -#include "Rtypes.h" // for Int_t, Double_t, Bool_t, UInt_t, etc - -class TGeoPNEntry; +// #include "MathUtils/Utils.h" +// #include "Rtypes.h" // for Int_t, Double_t, Bool_t, UInt_t, etc namespace o2 { namespace ft3 { -/// GeometryTGeo is a simple interface class to TGeoManager. It is used in the simulation -/// in order to query the TGeo FT3 geometry. -/// RS: In order to preserve the static character of the class but make it dynamically access -/// geometry, we need to check in every method if the structures are initialized. To be converted -/// to singleton at later stage. - -class GeometryTGeo : public o2::itsmft::GeometryTGeo +class GeometryTGeo : public o2::detectors::DetMatrixCache { public: - typedef o2::math_utils::Transform3D Mat3D; + using Mat3D = o2::math_utils::Transform3D; using DetMatrixCache::getMatrixL2G; using DetMatrixCache::getMatrixT2GRot; using DetMatrixCache::getMatrixT2L; // this method is not advised for ITS: for barrel detectors whose tracking frame is just a rotation // it is cheaper to use T2GRot using DetMatrixCache::getMatrixT2G; + GeometryTGeo(bool build = false, int loadTrans = 0); + ~GeometryTGeo(); + void Build(int loadTrans); + void fillMatrixCache(int mask); static GeometryTGeo* Instance() { @@ -64,29 +55,28 @@ class GeometryTGeo : public o2::itsmft::GeometryTGeo // adopt the unique instance from external raw pointer (to be used only to read saved instance from file) static void adopt(GeometryTGeo* raw); - // constructor - // ATTENTION: this class is supposed to behave as a singleton, but to make it root-persistent - // we must define public default constructor. - // NEVER use it, it will throw exception if the class instance was already created - // Use GeometryTGeo::Instance() instead - GeometryTGeo(bool build = kFALSE, int loadTrans = 0 - /*o2::base::utils::bit2Mask(o2::TransformType::T2L, // default transformations to load - o2::TransformType::T2G, - o2::TransformType::L2G)*/ - ); - - /// Default destructor - ~GeometryTGeo() override = default; - - GeometryTGeo(const GeometryTGeo& src) = delete; - GeometryTGeo& operator=(const GeometryTGeo& geom) = delete; - - // implement filling of the matrix cache - using o2::itsmft::GeometryTGeo::fillMatrixCache; - void fillMatrixCache(int mask) override; - + int extractNumberOfDiscs(int dir); + int extractNumberOfChips(int dir, int layer); + int extractChipId(std::string const volName); + void extractStaveChipId(std::string const volName, int& stave, int& chip); + void extractChipIds(std::string const volName, int& direction, int& layer, int& stave, int& chip); + + int getChipIndex(int dir, int disc, int stave, int chip) const; + // int getDisk(int index) const {return -1;} // TODO: implement this + int getLayer(int chipIdx) const; + std::string getMatrixPath(int direction, int layer, int stave, int chip) const; + int getNumberOfChips() const { return mSize; } + int getNumberOfLayers() const { return mNumberOfDiscs[0] + mNumberOfDiscs[1]; } + int getNumberOfStaves(int absDisc) const { return mNumberOfStavesPerDisc[absDisc]; } + int getSubDetID(int) const { return 2; } + int getStave(int chipIdx) const; + int getChipOnStave(int chipIdx) const; + int getStaveIdxDisc(int absDisc) const { return mStaveIdxDisc[absDisc]; } + int getChipIdxStave(int absStave) const { return mChipIdxStave[absStave]; } /// Exract FT3 parameters from TGeo - void Build(int loadTrans = 0) override; + + bool isOwner() const { return mOwner; } + void setOwner(bool v) { mOwner = v; } void Print(Option_t* opt = "") const; static const char* getFT3VolPattern() { return sVolumeName.c_str(); } @@ -106,15 +96,25 @@ class GeometryTGeo : public o2::itsmft::GeometryTGeo static std::string sVolumeName; ///< Mother volume name static std::string sLayerName; ///< Layer name static std::string sChipName; ///< Chip name - static std::string sSensorName; ///< Sensor name - static std::string sPassiveName; ///< Passive material name + static std::string sSensorName; ///< Sensor name + static std::string sPassiveName; ///< Passive material name - private: - static std::unique_ptr sInstance; ///< singletone instance + std::vector mCacheRefXDiscs; /// cache for X of ML and OT + std::vector mCacheRefAlphaDiscs; /// cache for sensor ref alpha ML and OT + std::vector mNumberOfDiscs; ///< Number Discs per direction + std::vector mNumberOfStavesPerDisc; /// TODO; in principle redundant? + std::vector mStaveIdxDisc; /// Index of first global stave Id for each disc + std::vector mChipIdxStave; /// Index of first chup for each global stave + std::vector mNumberOfChipsPerDisc; /// + // std::vector mChipIndexLayer; ///< ID of first chip in the layer + // std::vector mChipStaveIds; + + bool mOwner = true; //! is it owned by the singleton? - ClassDefOverride(GeometryTGeo, 1); // FT3 geometry based on TGeo + private: + static std::unique_ptr sInstance; ///< singleton instance }; + } // namespace ft3 } // namespace o2 - #endif \ No newline at end of file diff --git a/Detectors/Upgrades/ALICE3/FT3/base/src/FT3BaseLinkDef.h b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/src/FT3BaseLinkDef.h similarity index 100% rename from Detectors/Upgrades/ALICE3/FT3/base/src/FT3BaseLinkDef.h rename to Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/src/FT3BaseLinkDef.h diff --git a/Detectors/Upgrades/ALICE3/FT3/base/src/FT3BaseParam.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/src/FT3BaseParam.cxx similarity index 100% rename from Detectors/Upgrades/ALICE3/FT3/base/src/FT3BaseParam.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/src/FT3BaseParam.cxx diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/src/GeometryTGeo.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/src/GeometryTGeo.cxx new file mode 100644 index 0000000000000..9bfc465805777 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/base/src/GeometryTGeo.cxx @@ -0,0 +1,411 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +// TODO: clean up includes +#include +#include "MathUtils/Cartesian.h" + +#include // for LOG + +#include // for TGeoBBox +#include // for gGeoManager, TGeoManager +#include // for TGeoPNEntry, TGeoPhysicalNode +#include // for TGeoShape +#include // for Nint, ATan2, RadToDeg +#include // for TString, Form +#include "TClass.h" // for TClass +#include "TGeoMatrix.h" // for TGeoHMatrix +#include "TGeoNode.h" // for TGeoNode, TGeoNodeMatrix +#include "TGeoVolume.h" // for TGeoVolume +#include "TMathBase.h" // for Max +#include "TObjArray.h" // for TObjArray +#include "TObject.h" // for TObject + +#include // for isdigit +#include // for snprintf, NULL, printf +#include // for strstr, strlen + +using namespace TMath; +using namespace o2::detectors; + +namespace o2 +{ +namespace ft3 +{ +std::unique_ptr GeometryTGeo::sInstance; + +std::string GeometryTGeo::sVolumeName = "FT3V"; ///< Mother volume name +std::string GeometryTGeo::sInnerVolumeName = "FT3Inner"; ///< Mother inner volume name +std::string GeometryTGeo::sLayerName = "FT3Layer"; ///< Layer name +std::string GeometryTGeo::sChipName = "FT3Chip"; ///< Chip name +// TODO: this is now only used for the not-segmented version; synchronise? +std::string GeometryTGeo::sSensorName = "FT3Sensor"; ///< Sensor name +std::string GeometryTGeo::sPassiveName = "Passive"; ///< Passive material name + +GeometryTGeo::~GeometryTGeo() +{ + if (!mOwner) { + mOwner = true; + sInstance.release(); + } +} +//__________________________________________________________________________ +GeometryTGeo::GeometryTGeo(bool build, int loadTrans) : DetMatrixCache(detectors::DetID::FT3) +{ + // default c-tor, if build is true, the structures will be filled and the transform matrices + // will be cached + if (sInstance) { + LOG(fatal) << "Invalid use of public constructor: o2::ft3::GeometryTGeo instance exists"; + // throw std::runtime_error("Invalid use of public constructor: o2::ft3::GeometryTGeo instance exists"); + } + + if (build) { + Build(loadTrans); + } +} + +//__________________________________________________________________________ +void GeometryTGeo::Build(int loadTrans) +{ + if (isBuilt()) { + LOG(warning) << "Already built"; + return; // already initialized + } + + if (!gGeoManager) { + // RSTODO: in future there will be a method to load matrices from the CDB + LOG(fatal) << "Geometry is not loaded"; + } + + // Forward discs part + // int sensIdx = 0; + int totDiscs = 0; + int absStaveIdx = 0; + mSize = 0; + // TODO: clean up initialisation + if (mChipIdxStave.size() == 0) { + mChipIdxStave.push_back(0); + } + if (mStaveIdxDisc.size() == 0) { + mStaveIdxDisc.push_back(0); + } + for (int iDir = 0; iDir < 2; iDir++) { + mNumberOfDiscs.push_back(extractNumberOfDiscs(iDir)); + LOG(info) << "direction " << iDir << " has " << mNumberOfDiscs[iDir] << " discs"; + totDiscs += mNumberOfDiscs[iDir]; + + for (int iDisc = 0; iDisc < mNumberOfDiscs[iDir]; iDisc++) { + TGeoVolume* ft3V = gGeoManager->GetVolume(getFT3VolPattern()); + if (ft3V == nullptr) { + LOG(fatal) << getName() << " volume " << getFT3VolPattern() << " is not in the geometry"; + } + auto layerNode = ft3V->GetNode(Form("%s_1", composeSymNameLayer(iDir, iDisc))); + if (layerNode == nullptr) + LOG(fatal) << "Could not find layer node " << Form("%s_1", composeSymNameLayer(iDir, iDisc)); + auto layerVol = layerNode->GetVolume(); + if (layerVol == nullptr) + LOG(fatal) << "Could not find layer volume " << Form("%s_1", composeSymNameLayer(iDir, iDisc)); + TObjArray* nodes = layerVol->GetNodes(); + int nNodes = nodes->GetEntriesFast(); + int nStaves = 0; + int nSensor = 0; + std::vector chipsPerStave; + for (int j = 0; j < nNodes; j++) { + auto nd = dynamic_cast(nodes->At(j)); + const char* name = nd->GetName(); + if (strstr(name, "FT3Sensor") != nullptr && strstr(name, "Inactive") == nullptr) { + int direction = 0, layer = 0; + int stave = 0, chip = 0; + extractChipIds(name, direction, layer, stave, chip); + if (stave >= chipsPerStave.size()) { + chipsPerStave.resize(stave + 1, 0); + nStaves = stave + 1; + } + if (chip + 1 >= chipsPerStave[stave]) { + chipsPerStave[stave] = chip + 1; + } + nSensor++; + } + } + LOG(info) << "direction " << iDir << " disc " << iDisc << " has " << nNodes << " nodes of which " << nSensor << " sensors in " << chipsPerStave.size() << " staves"; + + if (nStaves != chipsPerStave.size()) + LOG(info) << "Inconsistency in stave count " << nStaves << " " << chipsPerStave.size(); + mChipIdxStave.resize(absStaveIdx + chipsPerStave.size() + 1); + mNumberOfStavesPerDisc.push_back(chipsPerStave.size()); // TODO: remove this? Or remove StaveIdxDisc + int totSensor = 0; + for (int nChips : chipsPerStave) { + LOG(debug) << "Absolute Stave ID " << absStaveIdx << " : " << nChips << " sensors"; + totSensor += nChips; + if (absStaveIdx) + mChipIdxStave[absStaveIdx + 1] = mChipIdxStave[absStaveIdx] + nChips; + absStaveIdx++; + } + if (totSensor != nSensor) + LOG(info) << "Inconsistency in sensor count " << nSensor << " " << totSensor; + LOG(debug) << " adding stave Idx " << absStaveIdx << " to disc array; element " << mStaveIdxDisc.size(); + mStaveIdxDisc.push_back(absStaveIdx); + mNumberOfChipsPerDisc.push_back(totSensor); + mSize += totSensor; + LOG(info) << "Total sensors so far " << mSize; + } + } + // mSize = mChipStaveIds.size(); + LOG(info) << "Total sensors " << mSize; + LOG(info) << "Length of stave-disc array " << mStaveIdxDisc.size(); + fillMatrixCache(loadTrans); // Check whether this causes trouble +} + +//__________________________________________________________________________ +const char* GeometryTGeo::composeSymNameLayer(int direction, int layerNumber) +{ + return Form("%s%d_%d", GeometryTGeo::getFT3LayerPattern(), direction, layerNumber); +} + +//__________________________________________________________________________ +const char* GeometryTGeo::composeSymNameChip(Int_t d, Int_t lr) +{ + return Form("%s/%s%d", composeSymNameLayer(d, lr), getFT3ChipPattern(), lr); +} + +//__________________________________________________________________________ +const char* GeometryTGeo::composeSymNameSensor(Int_t d, Int_t lr) +{ + return Form("%s/%s%d", composeSymNameChip(d, lr), getFT3SensorPattern(), lr); +} + +//__________________________________________________________________________ +int GeometryTGeo::extractNumberOfDiscs(int dir) +{ + int numDiscs = 0; + while (gGeoManager->GetVolume(composeSymNameLayer(dir, numDiscs))) { + numDiscs++; + } // Check maybe subvolume? + return numDiscs; // Assume same # layers on both sides +} +//__________________________________________________________________________ +int GeometryTGeo::extractNumberOfChips(int dir, int layer) +{ + int numSensors = 0; + TGeoVolume* ft3V = gGeoManager->GetVolume(getFT3VolPattern()); + if (ft3V == nullptr) { + LOG(fatal) << getName() << " volume " << getFT3VolPattern() << " is not in the geometry"; + } + auto layerVol = ft3V->GetNode(Form("%s_1", composeSymNameLayer(dir, layer)))->GetVolume(); + TObjArray* nodes = layerVol->GetNodes(); + int nNodes = nodes->GetEntriesFast(); + int nSensor = 0; + for (int j = 0; j < nNodes; j++) { + auto nd = dynamic_cast(nodes->At(j)); + const char* name = nd->GetName(); + if (strstr(name, "FT3Sensor") != nullptr && strstr(name, "Inactive") == nullptr) { + nSensor++; + } + } + LOG(info) << "direction " << dir << " layer " << layer << " has " << nNodes << " nodes of which " << nSensor << " sensors"; + return nSensor; +} +//__________________________________________________________________________ +int GeometryTGeo::extractChipId(std::string const volName) +{ + if (volName.find("FT3Sensor_Active") == 0) { + return std::stoi(volName.substr(volName.rfind('_') + 1)); + } + LOG(error) << "Not a sensor volume " << volName; + return -1; +} +void GeometryTGeo::extractStaveChipId(std::string const volName, int& stave, int& chip) +{ + if (volName.find("FT3Sensor_Active") == 0) { + int idx = volName.rfind('_'); + chip = std::stoi(volName.substr(idx + 1)); + idx = volName.rfind('_', idx); + stave = std::stoi(volName.substr(idx + 1)); + } else { + LOG(error) << "Not a sensor volume " << volName; + stave = -1; + chip = -1; + } +} +void GeometryTGeo::extractChipIds(std::string const volName, int& direction, int& layer, int& stave, int& chip) +{ + if (volName.find("FT3Sensor_Active") == 0) { + int idx = volName.find('_') + 1; + idx = volName.find('_', idx) + 1; + direction = std::stoi(volName.substr(idx)); + idx = volName.find('_', idx) + 1; + layer = std::stoi(volName.substr(idx)); + idx = volName.find('_', idx) + 1; + stave = std::stoi(volName.substr(idx)); + idx = volName.find('_', idx) + 1; + chip = std::stoi(volName.substr(idx)); + } else { + LOG(error) << "Not a sensor volume " << volName; + direction = -1; + } +} + +int GeometryTGeo::getChipIndex(int dir, int layer, int stave, int chip) const +{ + int absDisc = layer; + if (dir == 1) + absDisc += mNumberOfDiscs[0]; + return mChipIdxStave[mStaveIdxDisc[absDisc] + stave] + chip; +} + +int GeometryTGeo::getLayer(int chipIdx) const +{ + int lay = mNumberOfDiscs[0] + mNumberOfDiscs[1] - 1; + while (chipIdx < mChipIdxStave[mStaveIdxDisc[lay]] && lay > 0) { + lay--; + } + return lay; +} + +// retrieve local stave number from chip ID +int GeometryTGeo::getStave(int chipIdx) const +{ + int lay = getLayer(chipIdx); + int absStave = mStaveIdxDisc[lay]; + while (chipIdx >= mChipIdxStave[absStave] && absStave < mStaveIdxDisc[lay + 1]) { + absStave++; + } + return absStave - 1 - mStaveIdxDisc[lay]; +} + +// retrieve local chip number on stave from chip ID +int GeometryTGeo::getChipOnStave(int chipIdx) const +{ + int lay = getLayer(chipIdx); + int stave = getStave(chipIdx); + return chipIdx - mChipIdxStave[mStaveIdxDisc[lay] + stave]; +} + +std::string GeometryTGeo::getMatrixPath(int direction, int layer, int stave, int chip) const +{ + + // PrintChipID(index, subDetID, petalcase, disk, layer, stave, halfstave, mod, chip); + + std::string path = Form("/cave_1/barrel_1/%s_2/", GeometryTGeo::getFT3VolPattern()); + + // Stave name: std::string stave_volume_name = + // "Stave_" + std::to_string(i_stave) + "_" + std::to_string(layerNumber) + + // "_" + std::to_string(direction); + // Sensors directly placed in layer volume? + + path += Form("%s%d_%d_1/", getFT3LayerPattern(), direction, layer); // TRKLayerx_1 + // std::string sensorName = std::string("FT3Sensor_") + std::to_string(layer) + "_" + std::to_string(direction) + "_" + std::to_string(mChipStaveIds[index]) + "_" + index; + path += Form("FT3Sensor_Active_%d_%d_%d_%d_%d", direction, layer, stave, chip, chip); + /* + if (mLayoutMLOT == FT3Layout::kCylindrical) { + // TODO: fix this caser? + path += Form("%s%d_1/", getTRKSensorPattern(), layer); // TRKSensorx_1 + } else { + path += Form("%s%d_%d/", getFT3StavePattern(), layer, stave); + path += Form("%s%d_%d/", getFT3ModulePattern(), layer, mod); + path += Form("%s%d_%d_1", getFT3ChipPattern(), layer, chipID); + } + */ + return path; +} + +//__________________________________________________________________________ +void GeometryTGeo::fillMatrixCache(int mask) +{ + // populate matrix cache for requested transformations + // + if (mSize < 1) { + LOG(warning) << "The method Build was not called yet"; + Build(mask); + return; + } + + // build matrices + if ((mask & o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)) && !getCacheL2G().isFilled()) { + // Matrices for Local (Sensor!!! rather than the full chip) to Global frame transformation + LOGP(info, "Loading {} L2G matrices from TGeo; there are {} matrices", getName(), mSize); + auto& cacheL2G = getCacheL2G(); + cacheL2G.setSize(mSize); + auto& cacheT2L = getCacheT2L(); + cacheT2L.setSize(mSize); + mCacheRefAlphaDiscs.resize(mSize, 0); + + double locA[3] = {-100., 0., 0.}, locB[3] = {100., 0., 0.}, gloA[3], gloB[3]; + double xp{0}, yp{0}; + + gGeoManager->PushPath(); + LOG(info) << " Number of directions " << mNumberOfDiscs.size(); + int nTotDisc = mNumberOfDiscs[0] + mNumberOfDiscs[1]; + for (int absDisc = 0; absDisc < nTotDisc; absDisc++) { + int direction = 0; + int layer = absDisc; + if (absDisc >= mNumberOfDiscs[0]) { + direction = 1; + layer = absDisc - mNumberOfDiscs[0]; + } + LOG(info) << "Direction " << direction << " layer " << layer; + if (absDisc >= mNumberOfStavesPerDisc.size()) + LOG(fatal) << "Not enough entries in mNumberOfStavesPerDisc " << absDisc << " " << mNumberOfStavesPerDisc.size(); + for (int stave = 0; stave < mNumberOfStavesPerDisc[absDisc]; stave++) { + int absStave = mStaveIdxDisc[absDisc] + stave; + if (absStave + 1 >= mChipIdxStave.size()) + LOG(fatal) << "Attempting to get absStave + 1 from index array size " << mChipIdxStave.size(); + int nChip = mChipIdxStave[absStave + 1] - mChipIdxStave[absStave]; // TODO: this is too often == 0 + LOG(debug) << "Getting matrices for direction " << direction << " layer " << layer << " stave " << stave << " : " << nChip << " chips"; + for (int chip = 0; chip < nChip; chip++) { + int chipIdx = getChipIndex(direction, layer, stave, chip); + if (!gGeoManager->cd(getMatrixPath(direction, layer, stave, chip).c_str())) + LOG(fatal) << "Geometry path not found " << getMatrixPath(direction, layer, stave, chip); + const TGeoHMatrix* matL2G = gGeoManager->GetCurrentMatrix(); + if (chipIdx >= mSize) + LOG(fatal) << "ChipIdx " << chipIdx << " out of range " << mSize; + cacheL2G.setMatrix(Mat3D(*matL2G), chipIdx); + + matL2G->LocalToMaster(locA, gloA); + matL2G->LocalToMaster(locB, gloB); + double dx = gloB[0] - gloA[0], dy = gloB[1] - gloA[1]; + double t = (gloB[0] * dx + gloB[1] * dy) / (dx * dx + dy * dy); + xp = gloB[0] - dx * t; + yp = gloB[1] - dy * t; + float alp = std::atan2(yp, xp); + mCacheRefXDiscs.push_back(std::hypot(xp, yp)); + o2::math_utils::bringTo02Pi(alp); + mCacheRefAlphaDiscs[chipIdx] = alp; + + static TGeoHMatrix t2l; + t2l.Clear(); + t2l.RotateZ(mCacheRefAlphaDiscs[chipIdx] * TMath::RadToDeg()); // TODO: do we need this cache? + const TGeoHMatrix& matL2Gi = matL2G->Inverse(); + t2l.MultiplyLeft(&matL2Gi); + cacheT2L.setMatrix(Mat3D(t2l), chipIdx); // TODO: may need deref with * + } + } + } + gGeoManager->PopPath(); + } +} + +//__________________________________________________________________________ +void GeometryTGeo::Print(Option_t*) const +{ + if (!isBuilt()) { + LOGF(info, "Geometry not built yet!"); + return; + } + std::cout << "Detector ID: " << sInstance.get()->getDetID() << std::endl; + + LOGF(info, "Summary of GeometryTGeo: %s", getName()); + LOGF(info, "Number of disks: %d + %d", mNumberOfDiscs[0], mNumberOfDiscs[1]); + LOGF(info, "Total number of sensors: %d", mSize); +} + +} // namespace ft3 +} // namespace o2 \ No newline at end of file diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/CMakeLists.txt similarity index 91% rename from Detectors/Upgrades/ALICE3/FT3/simulation/CMakeLists.txt rename to Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/CMakeLists.txt index 23414d4ae7269..98adea7c6124a 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/CMakeLists.txt @@ -15,6 +15,8 @@ o2_add_library(FT3Simulation src/FT3Layer.cxx src/Detector.cxx PUBLIC_LINK_LIBRARIES O2::FT3Base + O2::TRKFT3Simulation + O2::DataFormatsTRKFT3 O2::ITSMFTSimulation ROOT::Physics) diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/data/simcuts.dat b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/data/simcuts.dat similarity index 100% rename from Detectors/Upgrades/ALICE3/FT3/simulation/data/simcuts.dat rename to Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/data/simcuts.dat diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/Detector.h b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/Detector.h similarity index 94% rename from Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/Detector.h rename to Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/Detector.h index 361d94463ef56..3779587b0f7a6 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/Detector.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/Detector.h @@ -20,7 +20,7 @@ #include "DetectorsBase/Detector.h" // for Detector #include "DetectorsBase/GeometryManager.h" // for getSensID #include "DetectorsCommonDataFormats/DetID.h" // for Detector -#include "ITSMFTSimulation/Hit.h" // for Hit +#include "DataFormatsTRKFT3/Hit.h" // for Hit #include "TArrayD.h" // for TArrayD #include "TGeoManager.h" // for gGeoManager, TGeoManager (ptr only) @@ -67,7 +67,7 @@ class Detector : public o2::base::DetImpl void Register() override; /// Gets the produced collections - std::vector* getHits(Int_t iColl) const + std::vector* getHits(Int_t iColl) const { if (iColl == 0) { return mHits; @@ -82,7 +82,7 @@ class Detector : public o2::base::DetImpl void ConstructGeometry() override; /// This method is an example of how to add your own point of type Hit to the clones array - o2::itsmft::Hit* addHit(int trackID, int detID, const TVector3& startPos, const TVector3& endPos, + o2::trkft3::Hit* addHit(int trackID, int detID, const TVector3& startPos, const TVector3& endPos, const TVector3& startMom, double startE, double endTime, double eLoss, unsigned char startStatus, unsigned char endStatus); @@ -116,7 +116,6 @@ class Detector : public o2::base::DetImpl protected: std::array, 2> mLayerName; // Two sets of layer names, one per direction (forward/backward) - std::unordered_map mActiveSensorMap; private: /// this is transient data about track passing the sensor @@ -129,7 +128,7 @@ class Detector : public o2::base::DetImpl } mTrackData; //! /// Container for hit data - std::vector* mHits; + std::vector* mHits; /// Create the detector materials virtual void createMaterials(); diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Layer.h b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3Layer.h similarity index 100% rename from Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Layer.h rename to Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3Layer.h diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3Module.h similarity index 100% rename from Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h rename to Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3Module.h diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h similarity index 100% rename from Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h rename to Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/Detector.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/Detector.cxx similarity index 97% rename from Detectors/Upgrades/ALICE3/FT3/simulation/src/Detector.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/Detector.cxx index b6cd65f28ea9e..7fe43975934f4 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/Detector.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/Detector.cxx @@ -15,7 +15,6 @@ #include "FT3Simulation/Detector.h" #include "DetectorsBase/Stack.h" -#include "ITSMFTSimulation/Hit.h" #include "SimulationDataFormat/TrackReference.h" #include "FT3Base/FT3BaseParam.h" @@ -51,13 +50,13 @@ class TGeoMedium; class TParticle; using namespace o2::ft3; -using o2::itsmft::Hit; +using o2::trkft3::Hit; //_________________________________________________________________________________________________ Detector::Detector() : o2::base::DetImpl("FT3", kTRUE), mTrackData(), - mHits(o2::utils::createSimVector()) + mHits(o2::utils::createSimVector()) { } @@ -349,7 +348,7 @@ void Detector::buildFT3Scoping() Detector::Detector(bool active) : o2::base::DetImpl("FT3", active), mTrackData(), - mHits(o2::utils::createSimVector()) + mHits(o2::utils::createSimVector()) { buildFT3ScopingV3(); // v3 Dec 25 } @@ -358,12 +357,10 @@ Detector::Detector(bool active) Detector::Detector(const Detector& rhs) : o2::base::DetImpl(rhs), mTrackData(), - /// Container for data points - mHits(o2::utils::createSimVector()) + mHits(o2::utils::createSimVector()) { mLayerName = rhs.mLayerName; - mActiveSensorMap = rhs.mActiveSensorMap; } //_________________________________________________________________________________________________ @@ -395,7 +392,6 @@ Detector& Detector::operator=(const Detector& rhs) base::Detector::operator=(rhs); mLayerName = rhs.mLayerName; - mActiveSensorMap = rhs.mActiveSensorMap; mLayers = rhs.mLayers; mTrackData = rhs.mTrackData; @@ -424,13 +420,6 @@ bool Detector::ProcessHits(FairVolume* vol) int volID = vol->getMCid(); - auto it = mActiveSensorMap.find(volID); - if (it == mActiveSensorMap.end()) { - return kFALSE; // Not a sensitive volume - } - - int lay = it->second; - auto stack = (o2::data::Stack*)fMC->GetStack(); bool startHit = false, stopHit = false; @@ -475,11 +464,16 @@ bool Detector::ProcessHits(FairVolume* vol) mTrackData.mTrkStatusStart = status; mTrackData.mHitStarted = true; } + static auto* geom = GeometryTGeo::Instance(); if (stopHit) { TLorentzVector positionStop; fMC->TrackPosition(positionStop); - // Retrieve the indices with the volume path - int chipindex = lay; + // Retrieve the chip index from the volume name + int chipindex = 0; + std::string volName = fMC->CurrentVolName(); + int direction = -1, layer = -1, stave = -1, chip = -1; + geom->extractChipIds(volName, direction, layer, stave, chip); + chipindex = geom->getChipIndex(direction, layer, stave, chip); Hit* p = addHit(stack->GetCurrentTrackNumber(), chipindex, mTrackData.mPositionStart.Vect(), positionStop.Vect(), mTrackData.mMomentumStart.Vect(), mTrackData.mMomentumStart.E(), positionStop.T(), @@ -616,6 +610,7 @@ void Detector::defineSensitiveVolumes() int nVolumes = allVolumes->GetEntriesFast(); LOG(info) << "Adding FT3 Sensitive Volumes by iterating over all geometry volumes..."; + static auto* geom = GeometryTGeo::Instance(); for (int direction : {IdxBackwardDisks, IdxForwardDisks}) { for (int iLayer = 0; iLayer < getNumberOfLayers(); iLayer++) { @@ -632,7 +627,7 @@ void Detector::defineSensitiveVolumes() // 3. SegmentedStave (format: FT3Sensor___...) // Add the trailing underscore to avoid confusing it with sig1 - std::string sig4 = "FT3Sensor_" + std::to_string(direction) + "_" + std::to_string(iLayer) + "_"; + std::string sig4 = "FT3Sensor_Active_" + std::to_string(direction) + "_" + std::to_string(iLayer) + "_"; // Iterate over all existing volumes to find matches for (int i = 0; i < nVolumes; ++i) { @@ -654,10 +649,6 @@ void Detector::defineSensitiveVolumes() if (isMatch) { AddSensitiveVolume(v); - int volID = gMC ? TVirtualMC::GetMC()->VolId(vName.c_str()) : 0; - if (volID > 0) { - mActiveSensorMap[volID] = iLayer; - } iSens++; } } diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3Layer.cxx similarity index 99% rename from Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3Layer.cxx index a4424f8ac1b7f..7fce06e029764 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3Layer.cxx @@ -32,7 +32,6 @@ class TGeoMedium; using namespace TMath; using namespace o2::ft3; -using namespace o2::itsmft; ClassImp(FT3Layer); diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3Module.cxx similarity index 99% rename from Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3Module.cxx index 4c9041b17fcec..d91ca5f83168e 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3Module.cxx @@ -132,8 +132,8 @@ std::pair calculate_y_range( } /* - * This function is a helper function which will pad out the stave with sensors - * until there is no more space available. + * This function is a helper function to determine the positions of sensors on the stave + * by adding sensors until there is no more space available. * * Arguments: * y_positions: a pair of vectors, where each vector contains pairs of @@ -735,7 +735,7 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction x_mid - Constants::active_width / 2, y_mid, z_mid, true); // right single sensor of the 2x1 addSingleSensorVolume( - motherVolume, layerNumber, direction, i_stave, sensor_count, + motherVolume, layerNumber, direction, i_stave, sensor_count + 1, x_mid + Constants::active_width / 2, y_mid, z_mid, false); // ------------ (2) Epoxy glue layer between silicon and copper (FPC) ------------ z_mid = z_offset_to_glue_Si * z_offset_multiplier + z_stave_shift; @@ -759,7 +759,7 @@ void FT3Module::create_layout_staveGeo(double mZ, int layerNumber, int direction x_mid, y_mid, z_mid, "CarbonKapton"); // increment to next sensor: (height + gap of one sensor) y_mid += y_sign * (Constants::sensor2x1_height + Constants::sensor2x1_gap); - sensor_count++; // same count for each material in the glued stack of materials + sensor_count += 2; // same count for each material in the glued stack of materials } // sensors in stack } // for y_sign (writing of positive or negative y positions) } // i_y_pos diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3SimulationLinkDef.h b/Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3SimulationLinkDef.h similarity index 100% rename from Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3SimulationLinkDef.h rename to Detectors/Upgrades/ALICE3/TRKFT3/FT3/simulation/src/FT3SimulationLinkDef.h diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/TRK/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/CMakeLists.txt new file mode 100644 index 0000000000000..a099bce5e022d --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/CMakeLists.txt @@ -0,0 +1,14 @@ +# Copyright 2019-2020 CERN and copyright holders of ALICE O2. +# See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +# All rights not expressly granted are reserved. +# +# This software is distributed under the terms of the GNU General Public +# License v3 (GPL Version 3), copied verbatim in the file "COPYING". +# +# In applying this license CERN does not waive the privileges and immunities +# granted to it by virtue of its status as an Intergovernmental Organization +# or submit itself to any jurisdiction. + +add_subdirectory(base) +add_subdirectory(macros) +add_subdirectory(simulation) diff --git a/Detectors/Upgrades/ALICE3/TRK/README.md b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/README.md similarity index 69% rename from Detectors/Upgrades/ALICE3/TRK/README.md rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/README.md index efe07ab092eb2..8cf4053fd325b 100644 --- a/Detectors/Upgrades/ALICE3/TRK/README.md +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/README.md @@ -15,12 +15,16 @@ Configurables for various sub-detectors are presented in the following Table: | Subsystem | Available options | Comments | | ------------------ | ------------------------------------------------------- | ---------------------------------------------------------------- | | `TRKBase.layoutVD` | `kIRIS4` (default), `kIRISFullCyl`, `kIRIS5`, `kIRIS4a` | [link to definitions](./base/include/TRKBase/TRKBaseParam.h) | -| `TRKBase.layoutMLOT` | `kCylindrical`, `kSegmented` (default) | `kSegmented` produces a Turbo layout for ML and a Staggered layout for OT | +| `TRKBase.layoutMLOT` | `kCylindrical`, `kSegmented` (default) | `kSegmented` produces a Turbo layout for ML and a Staggered layout for OT | | `TRKBase.layoutSRV` | `kPeacockv1` (default), `kLOISymm` | `kLOISymm` produces radially symmetric service volumes, as used in the LoI | +| `TRKBase.disableFT3` | `false` (default), `true` | toggle to disable the forward disks | +| `TRKBase.layoutFT3` | `kSegmentedStave` (default), `kSegmentedFT3`, `kTrapezoidal` | disk geometry settings `kSegmentedFT3` refers to an outdated segmentation | +| `TRKBase.nTrapezoidalSegments` | integer; default: 32 | number of trapezoidal segments in the disks for kTrapezoidal layout | + For example, a geometry with fully cylindrical tracker barrel (for all layers in VD, ML and OT) can be obtained by ```bash -o2-sim-serial-run5 -n 1 -g pythia8hi -m A3IP TRK FT3 TF3 \ +o2-sim-serial-run5 -n 1 -g pythia8hi -m A3IP TRK TF3 \ --configKeyValues "TRKBase.layoutVD=kIRISFullCyl;TRKBase.layoutMLOT=kCylindrical" ``` @@ -44,15 +48,15 @@ When `TRKBase.layoutMLOT=kCylindrical` is used, each layer requires a minimum of **Example for `kCylindrical`:** ```text / Configuration for kCylindrical layout - ALICE3 TRK -/ rInn length thick [optional_mode] -7.0 127.985 0.1 -9.0 127.985 0.1 -12.0 127.985 0.1 -20.0 127.985 0.1 -30.0 127.985 0.1 -45.0 255.9 0.1 -60.0 255.9 0.1 -80.0 255.9 0.1 +/ rInn length thick [optional_mode] +7.0 127.985 0.1 +9.0 127.985 0.1 +12.0 127.985 0.1 +20.0 127.985 0.1 +30.0 127.985 0.1 +45.0 255.9 0.1 +60.0 255.9 0.1 +80.0 255.9 0.1 ``` ### 2. Segmented Layout (`kSegmented`) @@ -74,20 +78,24 @@ From the 6th valid line onwards, lines are parsed as `TRKOTLayer` objects. These ```text / Configuration for kSegmented layout - ALICE3 TRK / --- ML LAYERS (Indices 0 to 4) --- -/ rInn thick tilt nStaves nMods stagOffset [optional_mode] -7.0 0.01 11.2 10 11 0.0 1 -9.0 0.01 11.9 14 11 0.0 1 -12.0 0.01 11.4 18 11 0.0 1 -20.0 0.01 0.0 26 11 1.17 1 -30.0 0.01 0.0 38 11 0.89 1 +/ rInn thick tilt nStaves nMods stagOffset [optional_mode] +7.0 0.01 11.2 10 11 0.0 1 +9.0 0.01 11.9 14 11 0.0 1 +12.0 0.01 11.4 18 11 0.0 1 +20.0 0.01 0.0 26 11 1.17 1 +30.0 0.01 0.0 38 11 0.89 1 / / --- OT LAYERS (Indices 5 to 7) --- / Outer layers do NOT have stagOffset. -/ rInn thick tilt nStaves nMods [optional_mode] -45.0 0.01 0.0 32 22 1 -60.0 0.01 0.0 42 22 1 -80.0 0.01 0.0 56 22 1 +/ rInn thick tilt nStaves nMods [optional_mode] +45.0 0.01 0.0 32 22 1 +60.0 0.01 0.0 42 22 1 +80.0 0.01 0.0 56 22 1 ``` +## Additional options for forward disks + +Furthermore, there are more options in the case of stave segmentation -- for only OT or both. The user can set to cut the staves exactly on the nominal inner radii (true by default), and outer radii (false by default) of the disks. This exists since (planned) placements of sensors & staves often protrude out of the nominal radii to be more able to cover the nominal disk area. In addition, it is possible to draw reference circles (`TRKBase.drawReferenceCircles`) in root for the stave segmented layouts for both the inner (red) and outer (blue) radii. This is off by default, yet can be toggled if the user wants to see how tight the tiling is to the nominal radii -- for visualisation purposes only. + diff --git a/Detectors/Upgrades/ALICE3/TRK/base/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/CMakeLists.txt similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/base/CMakeLists.txt rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/CMakeLists.txt diff --git a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/AlmiraParam.h b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/include/TRKBase/AlmiraParam.h similarity index 94% rename from Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/AlmiraParam.h rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/include/TRKBase/AlmiraParam.h index 9929a14c4e39c..53d4792f7c2a5 100644 --- a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/AlmiraParam.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/include/TRKBase/AlmiraParam.h @@ -25,7 +25,8 @@ namespace trk { struct AlmiraParam : public o2::conf::ConfigurableParamHelper { - static constexpr size_t kNLayers = constants::VD::petal::nLayers + constants::ML::nLayers + constants::OT::nLayers; + // This should be part of geometryTGeo + static constexpr size_t kNLayers = constants::VD::petal::nLayers + constants::ML::nLayers + constants::OT::nLayers + constants::MLOTDisks::nLayers; static constexpr size_t getNLayers() { return kNLayers; } int roFrameLengthInBCPerLayer[kNLayers] = {0}; ///< ROF length in BC per layer diff --git a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/GeometryTGeo.h b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/include/TRKBase/GeometryTGeo.h similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/GeometryTGeo.h rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/include/TRKBase/GeometryTGeo.h diff --git a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/SegmentationChip.h b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/include/TRKBase/SegmentationChip.h similarity index 98% rename from Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/SegmentationChip.h rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/include/TRKBase/SegmentationChip.h index 7ee569c9bd8e8..fac5198966fd6 100644 --- a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/SegmentationChip.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/include/TRKBase/SegmentationChip.h @@ -116,7 +116,7 @@ class SegmentationChip maxWidth = constants::VD::petal::layer::width[layer]; maxLength = constants::VD::petal::layer::length; // TODO: change this to use the layer and disk - } else if (subDetID == 1) { + } else if (subDetID == 1 || subDetID == 2) { pitchRow = PitchRowMLOT; pitchCol = PitchColMLOT; maxWidth = constants::moduleMLOT::chip::width - constants::moduleMLOT::chip::passiveEdgeReadOut; @@ -135,7 +135,7 @@ class SegmentationChip maxWidth = constants::VD::petal::layer::width[layer]; maxLength = constants::VD::petal::layer::length; // TODO: change this to use the layer and disk - } else if (subDetID == 1) { // ML/OT + } else if (subDetID == 1 || subDetID == 2) { // ML/OT maxWidth = constants::moduleMLOT::chip::width - constants::moduleMLOT::chip::passiveEdgeReadOut; maxLength = constants::moduleMLOT::chip::length; } @@ -151,7 +151,7 @@ class SegmentationChip nRows = constants::VD::petal::layer::nRows[layer]; nCols = constants::VD::petal::layer::nCols; // TODO: change this to use the layer and disk - } else if (subDetID == 1) { + } else if (subDetID == 1 || subDetID == 2) { nRows = constants::moduleMLOT::chip::nRows; nCols = constants::moduleMLOT::chip::nCols; } @@ -196,7 +196,7 @@ class SegmentationChip if (subDetID == 0) { xRow = 0.5 * (constants::VD::petal::layer::width[layer] - PitchRowVD) - (row * PitchRowVD); zCol = col * PitchColVD + 0.5 * (PitchColVD - constants::VD::petal::layer::length); - } else if (subDetID == 1) { // ML/OT + } else if (subDetID == 1 || subDetID == 2) { // ML/OT xRow = 0.5 * (constants::moduleMLOT::chip::width - constants::moduleMLOT::chip::passiveEdgeReadOut - PitchRowMLOT) - (row * PitchRowMLOT); zCol = col * PitchColMLOT + 0.5 * (PitchColMLOT - constants::moduleMLOT::chip::length); } diff --git a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/Specs.h b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/include/TRKBase/Specs.h similarity index 98% rename from Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/Specs.h rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/include/TRKBase/Specs.h index b484e13f3546e..be6d5c8808275 100644 --- a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/Specs.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/include/TRKBase/Specs.h @@ -142,6 +142,12 @@ constexpr double pitchZ{10.0 * mu}; constexpr double responseYShift{5 * mu}; /// center of the epitaxial layer constexpr double thickness{20 * mu}; } // namespace alice3resp + +namespace MLOTDisks +{ +constexpr int nLayers{12}; // number of disks in the ML and OT +} + } // namespace o2::trk::constants #endif diff --git a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/TRKBaseParam.h b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/include/TRKBase/TRKBaseParam.h similarity index 74% rename from Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/TRKBaseParam.h rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/include/TRKBase/TRKBaseParam.h index 65194ad6edfcb..1b5efef11fc2a 100644 --- a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/TRKBaseParam.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/include/TRKBase/TRKBaseParam.h @@ -43,6 +43,20 @@ struct TRKBaseParam : public o2::conf::ConfigurableParamHelper { bool irisOpen = false; bool includeLowServices = false; + // Options for forward disks (FT3) + int nTrapezoidalSegments = 32; // for the simple trapezoidal disks + // Forward discs: define tolerance allowed for staves to go outside nominal radii + double staveTolFT3MLInner = 0.; + double staveTolFT3MLOuter = 0.; + double staveTolFT3OTInner = 0.; + double staveTolFT3OTOuter = 0.; + + // Forward discs: toggle to center staves at x=0 line + bool placeSensorStackInMiddleOfStave = false; + + // Draw reference circles at inner and outer radius of forward discs for visualisation + bool drawReferenceCircles = false; + eVDLayout layoutVD = kIRIS4; // VD detector layout design eMLOTLayout layoutMLOT = kSegmented; // ML and OT detector layout design eSrvLayout layoutSRV = kPeacockv1; // Layout of services diff --git a/Detectors/Upgrades/ALICE3/TRK/base/src/AlmiraParam.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/src/AlmiraParam.cxx similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/base/src/AlmiraParam.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/src/AlmiraParam.cxx diff --git a/Detectors/Upgrades/ALICE3/TRK/base/src/GeometryTGeo.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/src/GeometryTGeo.cxx similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/base/src/GeometryTGeo.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/src/GeometryTGeo.cxx diff --git a/Detectors/Upgrades/ALICE3/TRK/base/src/SegmentationChip.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/src/SegmentationChip.cxx similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/base/src/SegmentationChip.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/src/SegmentationChip.cxx diff --git a/Detectors/Upgrades/ALICE3/TRK/base/src/TRKBaseLinkDef.h b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/src/TRKBaseLinkDef.h similarity index 95% rename from Detectors/Upgrades/ALICE3/TRK/base/src/TRKBaseLinkDef.h rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/src/TRKBaseLinkDef.h index e36955cdd150d..7bddc1cddd37c 100644 --- a/Detectors/Upgrades/ALICE3/TRK/base/src/TRKBaseLinkDef.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/src/TRKBaseLinkDef.h @@ -19,7 +19,7 @@ #pragma link C++ class o2::conf::ConfigurableParamHelper < o2::trk::TRKBaseParam> + ; #pragma link C++ class o2::trk::AlmiraParam + ; -#pragma link C++ class o2::trk::GeometryTGeo + +#pragma link C++ class o2::trk::GeometryTGeo; #pragma link C++ class o2::trk::TRKBaseParam + ; #pragma link C++ class o2::trk::SegmentationChip + ; diff --git a/Detectors/Upgrades/ALICE3/TRK/base/src/TRKBaseParam.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/src/TRKBaseParam.cxx similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/base/src/TRKBaseParam.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/base/src/TRKBaseParam.cxx diff --git a/Detectors/Upgrades/ALICE3/TRK/macros/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/CMakeLists.txt similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/macros/CMakeLists.txt rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/CMakeLists.txt diff --git a/Detectors/Upgrades/ALICE3/TRK/macros/test/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/CMakeLists.txt similarity index 95% rename from Detectors/Upgrades/ALICE3/TRK/macros/test/CMakeLists.txt rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/CMakeLists.txt index cdae7c9c379fd..6dcbfc8d65d6b 100644 --- a/Detectors/Upgrades/ALICE3/TRK/macros/test/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/CMakeLists.txt @@ -43,7 +43,7 @@ o2_add_test_root_macro(CheckTracksCA.C LABELS trk COMPILE_ONLY) o2_add_test_root_macro(CheckClusters.C - PUBLIC_LINK_LIBRARIES O2::DataFormatsTRK + PUBLIC_LINK_LIBRARIES O2::DataFormatsTRKFT3 O2::SimulationDataFormat O2::Framework O2::TRKBase @@ -51,7 +51,7 @@ o2_add_test_root_macro(CheckClusters.C LABELS trk COMPILE_ONLY) o2_add_test_root_macro(postClusterSizeVsEta.C - PUBLIC_LINK_LIBRARIES O2::DataFormatsTRK + PUBLIC_LINK_LIBRARIES O2::DataFormatsTRKFT3 O2::SimulationDataFormat O2::Framework O2::TRKBase diff --git a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckBandwidth.C b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/CheckBandwidth.C similarity index 99% rename from Detectors/Upgrades/ALICE3/TRK/macros/test/CheckBandwidth.C rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/CheckBandwidth.C index c071a06516d30..f92c161e682b3 100644 --- a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckBandwidth.C +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/CheckBandwidth.C @@ -28,11 +28,11 @@ #include #include "TRKBase/GeometryTGeo.h" -#include "DataFormatsITSMFT/Digit.h" +#include "DataFormatsTRKFT3/Digit.h" #include "MathUtils/Utils.h" #include "DetectorsBase/GeometryManager.h" -#include "DataFormatsITSMFT/ROFRecord.h" +#include "DataFormatsTRKFT3/ROFRecord.h" #include "CommonDataFormat/InteractionRecord.h" #include "SimulationDataFormat/DigitizationContext.h" @@ -169,8 +169,8 @@ void CheckBandwidth(std::string digifile = "trkdigits.root", std::string inputGe TTree* digTree = (TTree*)digFile->Get("o2sim"); const int nDigitTreeEntries = digTree->GetEntries(); - std::vector*> digArr(nTotalLayers, nullptr); - std::vector*> rofRecords(nTotalLayers, nullptr); + std::vector*> digArr(nTotalLayers, nullptr); + std::vector*> rofRecords(nTotalLayers, nullptr); for (int nDigitsLayer{0}; nDigitsLayer < nTotalLayers; ++nDigitsLayer) { if (!digTree->GetBranch(Form("TRKDigit_%i", nDigitsLayer))) { break; diff --git a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckClusters.C b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/CheckClusters.C similarity index 95% rename from Detectors/Upgrades/ALICE3/TRK/macros/test/CheckClusters.C rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/CheckClusters.C index 7b9365dbe2011..a6adf3c6ba6aa 100644 --- a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckClusters.C +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/CheckClusters.C @@ -12,6 +12,22 @@ /// \file CheckClusters.C /// \brief Macro to check TRK clusters and compare cluster positions to MC hit positions +#ifndef ENABLE_UPGRADES +#include +#include + +void CheckClusters(const std::string& = "o2clus_trk.root", + const std::string& = "o2sim_HitsTRK.root", + const std::string& = "o2sim_geometry.root", + const std::string& = "http://alice-ccdb.cern.ch", + long = -1, + bool = false) +{ + std::cerr << "CheckClusters requires a build with ENABLE_UPGRADES" << std::endl; +} + +#else + #if !defined(__CLING__) || defined(__ROOTCLING__) #include #include @@ -29,12 +45,12 @@ #include #include -#include "DataFormatsTRK/Cluster.h" -#include "DataFormatsTRK/ROFRecord.h" +#include "DataFormatsTRKFT3/Cluster.h" +#include "DataFormatsTRKFT3/Hit.h" +#include "DataFormatsTRKFT3/ROFRecord.h" #include "TRKBase/AlmiraParam.h" #include "TRKBase/GeometryTGeo.h" #include "TRKBase/SegmentationChip.h" -#include "TRKSimulation/Hit.h" #include "ITSMFTSimulation/AlpideSimResponse.h" #include "CCDB/BasicCCDBManager.h" #include "MathUtils/Cartesian.h" @@ -53,7 +69,7 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", { gROOT->SetBatch(batch); - using HitVec = std::vector; + using HitVec = std::vector; using MC2HITS_map = std::unordered_map>; // maps (trackID << 32) + chipID -> hit indices // ── Chip response (for hit-segment propagation to charge-collection plane) ── @@ -151,8 +167,8 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", // Read per-layer cluster branches and accumulate static constexpr int nLayers = o2::trk::AlmiraParam::kNLayers; - std::vector*> clusArrPerLayer(nLayers, nullptr); - std::vector*> rofRecVecPerLayer(nLayers, nullptr); + std::vector*> clusArrPerLayer(nLayers, nullptr); + std::vector*> rofRecVecPerLayer(nLayers, nullptr); std::vector*> patternsPerLayer(nLayers, nullptr); std::vector*> clusLabArrPerLayer(nLayers, nullptr); std::vector> patternOffsetsPerLayer(nLayers); @@ -350,7 +366,7 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", float clLocX{0.f}, clLocZ{0.f}; o2::trk::SegmentationChip::detectorToLocalUnchecked( cluster.row, cluster.col, clLocX, clLocZ, - cluster.subDetID, cluster.layer, cluster.disk); + cluster.subDetID, cluster.layer, cluster.layer); const float pitchRow = (cluster.subDetID == 0) ? o2::trk::SegmentationChip::PitchRowVD : o2::trk::SegmentationChip::PitchRowMLOT; @@ -377,7 +393,7 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", (float)gloC.X(), (float)gloC.Y(), (float)gloC.Z(), clLocX, clLocZ, (float)rofRec.getROFrame(), (float)cluster.size, (float)cluster.chipID, - (float)cluster.layer, (float)cluster.disk, (float)cluster.subDetID, + (float)cluster.layer, -1.f, (float)cluster.subDetID, (float)cluster.row, (float)cluster.col, -1.f}; nt.Fill(data.data()); continue; @@ -405,7 +421,7 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", nNoMCHit++; continue; } - auto projectHitToResponsePlane = [&](const o2::trk::Hit& hit, float& hitLocX, float& hitLocZ) { + auto projectHitToResponsePlane = [&](const o2::trkft3::Hit& hit, float& hitLocX, float& hitLocZ) { const auto& gloHend = hit.GetPos(); const auto& gloHsta = hit.GetPosStart(); o2::math_utils::Point3D locHsta = gman->getMatrixL2G(cluster.chipID) ^ (gloHsta); // inverse L2G @@ -430,7 +446,7 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", } }; - const o2::trk::Hit* bestHit = nullptr; + const o2::trkft3::Hit* bestHit = nullptr; float hitLocX{0.f}, hitLocZ{0.f}; float bestDist2 = std::numeric_limits::max(); for (const auto ih : hitEntry->second) { @@ -470,7 +486,7 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", (float)gloC.X(), (float)gloC.Y(), (float)gloC.Z(), clLocX, clLocZ, (float)rofRec.getROFrame(), (float)cluster.size, (float)cluster.chipID, - (float)cluster.layer, (float)cluster.disk, (float)cluster.subDetID, + (float)cluster.layer, -1.f, (float)cluster.subDetID, (float)cluster.row, (float)cluster.col, pt}; nt.Fill(data.data()); } @@ -523,3 +539,5 @@ void CheckClusters(const std::string& clusfile = "o2clus_trk.root", LOGP(info, "Output saved to CheckClusters.root and PNG files"); } + +#endif diff --git a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckDigitsTRK.C b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/CheckDigitsTRK.C similarity index 98% rename from Detectors/Upgrades/ALICE3/TRK/macros/test/CheckDigitsTRK.C rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/CheckDigitsTRK.C index 400457fc98585..ee463f304405e 100644 --- a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckDigitsTRK.C +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/CheckDigitsTRK.C @@ -24,8 +24,8 @@ #include "TRKBase/SegmentationChip.h" #include "TRKBase/GeometryTGeo.h" -#include "DataFormatsITSMFT/Digit.h" -#include "TRKSimulation/Hit.h" +#include "DataFormatsTRKFT3/Digit.h" +#include "DataFormatsTRKFT3/Hit.h" #include "MathUtils/Utils.h" #include "SimulationDataFormat/ConstMCTruthContainer.h" #include "SimulationDataFormat/IOMCTruthContainerView.h" @@ -34,7 +34,7 @@ #include "ITSMFTSimulation/AlpideSimResponse.h" #include "CCDB/BasicCCDBManager.h" -#include "DataFormatsITSMFT/ROFRecord.h" +#include "DataFormatsTRKFT3/ROFRecord.h" #endif @@ -82,8 +82,8 @@ void CheckDigits(std::string digifile = "trkdigits.root", std::string hitfile = using namespace o2::base; using namespace o2::trk; - using o2::itsmft::Digit; - using o2::trk::Hit; + using o2::trkft3::Digit; + using o2::trkft3::Hit; using o2::trk::SegmentationChip; @@ -117,7 +117,7 @@ void CheckDigits(std::string digifile = "trkdigits.root", std::string hitfile = TFile* hitFile = TFile::Open(hitfile.data()); TTree* hitTree = (TTree*)hitFile->Get("o2sim"); int nevH = hitTree->GetEntries(); // hits are stored as one event per entry - std::vector*> hitArray(nevH, nullptr); + std::vector*> hitArray(nevH, nullptr); std::vector> mc2hitVec(nevH); @@ -126,8 +126,8 @@ void CheckDigits(std::string digifile = "trkdigits.root", std::string hitfile = TTree* digTree = (TTree*)digFile->Get("o2sim"); int nDigitLayers = 0; - std::vector*> digArr(nTotalLayers, nullptr); - std::vector*> rofRecordsArr(nTotalLayers, nullptr); + std::vector*> digArr(nTotalLayers, nullptr); + std::vector*> rofRecordsArr(nTotalLayers, nullptr); std::vector plabelsArr(nTotalLayers, nullptr); for (int iLayer = 0; iLayer < nTotalLayers; ++iLayer) { diff --git a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckTracksCA.C b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/CheckTracksCA.C similarity index 99% rename from Detectors/Upgrades/ALICE3/TRK/macros/test/CheckTracksCA.C rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/CheckTracksCA.C index f7917ca4203f1..708701789f483 100644 --- a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckTracksCA.C +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/CheckTracksCA.C @@ -41,7 +41,7 @@ #include "SimulationDataFormat/MCCompLabel.h" #include "SimulationDataFormat/MCTrack.h" #include "Steer/MCKinematicsReader.h" -#include "TRKSimulation/Hit.h" +#include "DataFormatsTRKFT3/Hit.h" #include "TRKBase/GeometryTGeo.h" #include "DetectorsBase/GeometryManager.h" @@ -161,7 +161,7 @@ void CheckTracksCA(std::string trackfile = "o2trac_trk.root", o2::base::GeometryManager::loadGeometry(); auto* gman = o2::trk::GeometryTGeo::Instance(); - std::vector* trkHit = nullptr; + std::vector* trkHit = nullptr; hitsTree->SetBranchAddress("TRKHit", &trkHit); Long64_t nHitsEntries = hitsTree->GetEntries(); diff --git a/Detectors/Upgrades/ALICE3/TRK/macros/test/postClusterSizeVsEta.C b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/postClusterSizeVsEta.C similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/macros/test/postClusterSizeVsEta.C rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/postClusterSizeVsEta.C diff --git a/Detectors/Upgrades/ALICE3/TRK/macros/test/run_test.sh b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/run_test.sh similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/macros/test/run_test.sh rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/macros/test/run_test.sh diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/CMakeLists.txt similarity index 60% rename from Detectors/Upgrades/ALICE3/TRK/simulation/CMakeLists.txt rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/CMakeLists.txt index 6d30d8d01bb12..0760504c4cf3f 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/CMakeLists.txt @@ -10,33 +10,22 @@ # or submit itself to any jurisdiction. o2_add_library(TRKSimulation - SOURCES src/Hit.cxx - src/TRKLayer.cxx - src/ChipDigitsContainer.cxx - src/ChipSimResponse.cxx + SOURCES src/TRKLayer.cxx src/Detector.cxx - src/DigiParams.cxx - src/Digitizer.cxx src/TRKServices.cxx - src/DPLDigitizerParam.cxx src/VDLayer.cxx src/VDGeometryBuilder.cxx PUBLIC_LINK_LIBRARIES O2::TRKBase - O2::FT3Simulation + O2::TRKFT3Simulation + O2::DataFormatsTRKFT3 O2::ITSMFTSimulation O2::DetectorsRaw O2::SimulationDataFormat) o2_target_root_dictionary(TRKSimulation - HEADERS include/TRKSimulation/Hit.h - include/TRKSimulation/ChipDigitsContainer.h - include/TRKSimulation/ChipSimResponse.h - include/TRKSimulation/DigiParams.h - include/TRKSimulation/Digitizer.h - include/TRKSimulation/Detector.h + HEADERS include/TRKSimulation/Detector.h include/TRKSimulation/TRKLayer.h include/TRKSimulation/TRKServices.h include/TRKSimulation/VDLayer.h include/TRKSimulation/VDGeometryBuilder.h - include/TRKSimulation/VDSensorRegistry.h - include/TRKSimulation/DPLDigitizerParam.h) + include/TRKSimulation/VDSensorRegistry.h) diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Detector.h b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/Detector.h similarity index 79% rename from Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Detector.h rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/Detector.h index 9666916800185..a7972d14191a6 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Detector.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/Detector.h @@ -13,7 +13,7 @@ #define ALICEO2_TRK_DETECTOR_H #include "DetectorsBase/Detector.h" -#include "TRKSimulation/Hit.h" +#include "DataFormatsTRKFT3/Hit.h" #include "TRKSimulation/TRKLayer.h" #include "TRKSimulation/TRKServices.h" @@ -43,9 +43,9 @@ class Detector : public o2::base::DetImpl void ConstructGeometry() override; - o2::trk::Hit* addHit(int trackID, unsigned short detID, const TVector3& startPos, const TVector3& endPos, - const TVector3& startMom, double startE, double endTime, double eLoss, - unsigned char startStatus, unsigned char endStatus); + o2::trkft3::Hit* addHit(int trackID, unsigned short detID, const TVector3& startPos, const TVector3& endPos, + const TVector3& startMom, double startE, double endTime, double eLoss, + unsigned char startStatus, unsigned char endStatus); // Mandatory overrides void BeginPrimary() override { ; } @@ -59,7 +59,7 @@ class Detector : public o2::base::DetImpl void Reset() override; // Custom member functions - std::vector* getHits(int iColl) const + std::vector* getHits(int iColl) const { if (!iColl) { return mHits; @@ -81,14 +81,14 @@ class Detector : public o2::base::DetImpl // Transient data about track passing the sensor struct TrackData { - bool mHitStarted; // hit creation started - unsigned char mTrkStatusStart; // track status flag - TLorentzVector mPositionStart; // position at entrance - TLorentzVector mMomentumStart; // momentum - double mEnergyLoss; // energy loss - } mTrackData; //! transient data - GeometryTGeo* mGeometryTGeo; //! - std::vector* mHits; // Derived from ITSMFT + bool mHitStarted; // hit creation started + unsigned char mTrkStatusStart; // track status flag + TLorentzVector mPositionStart; // position at entrance + TLorentzVector mMomentumStart; // momentum + double mEnergyLoss; // energy loss + } mTrackData; //! transient data + GeometryTGeo* mGeometryTGeo; //! + std::vector* mHits; std::vector> mLayers; TRKServices mServices; // Houses the services of the TRK, but not the Iris tracker diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKLayer.h b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/TRKLayer.h similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKLayer.h rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/TRKLayer.h diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKServices.h b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/TRKServices.h similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKServices.h rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/TRKServices.h diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/VDGeometryBuilder.h b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/VDGeometryBuilder.h similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/VDGeometryBuilder.h rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/VDGeometryBuilder.h diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/VDLayer.h b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/VDLayer.h similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/VDLayer.h rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/VDLayer.h diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/VDSensorRegistry.h b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/VDSensorRegistry.h similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/VDSensorRegistry.h rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/include/TRKSimulation/VDSensorRegistry.h diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/Detector.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/Detector.cxx similarity index 97% rename from Detectors/Upgrades/ALICE3/TRK/simulation/src/Detector.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/Detector.cxx index 196727b2c140f..eead9a7571fd3 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/Detector.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/Detector.cxx @@ -15,7 +15,7 @@ #include "TRKBase/Specs.h" #include "TRKBase/TRKBaseParam.h" -#include "TRKSimulation/Hit.h" +#include "DataFormatsTRKFT3/Hit.h" #include "TRKSimulation/VDGeometryBuilder.h" #include "TRKSimulation/VDSensorRegistry.h" #include @@ -27,7 +27,7 @@ #include #include -using o2::trk::Hit; +using o2::trkft3::Hit; namespace o2 { @@ -42,14 +42,14 @@ float getDetLengthFromEta(const float eta, const float radius) Detector::Detector() : o2::base::DetImpl("TRK", true), mTrackData(), - mHits(o2::utils::createSimVector()) + mHits(o2::utils::createSimVector()) { } Detector::Detector(bool active) : o2::base::DetImpl("TRK", true), mTrackData(), - mHits(o2::utils::createSimVector()) + mHits(o2::utils::createSimVector()) { auto& trkPars = TRKBaseParam::Instance(); @@ -70,7 +70,7 @@ Detector::Detector(bool active) Detector::Detector(const Detector& other) : o2::base::DetImpl(other), mTrackData(), - mHits(o2::utils::createSimVector()) + mHits(o2::utils::createSimVector()) { } @@ -584,9 +584,9 @@ bool Detector::ProcessHits(FairVolume* vol) return true; } -o2::trk::Hit* Detector::addHit(int trackID, unsigned short detID, const TVector3& startPos, const TVector3& endPos, - const TVector3& startMom, double startE, double endTime, double eLoss, unsigned char startStatus, - unsigned char endStatus) +o2::trkft3::Hit* Detector::addHit(int trackID, unsigned short detID, const TVector3& startPos, const TVector3& endPos, + const TVector3& startMom, double startE, double endTime, double eLoss, unsigned char startStatus, + unsigned char endStatus) { mHits->emplace_back(trackID, detID, startPos, endPos, startMom, startE, endTime, eLoss, startStatus, endStatus); return &(mHits->back()); diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/TRKLayer.cxx similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/TRKLayer.cxx diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/TRKServices.cxx similarity index 99% rename from Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/TRKServices.cxx index e3855dbde6535..9cef844e37e58 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/TRKServices.cxx @@ -21,8 +21,6 @@ #include #include -#include - #include #include @@ -922,7 +920,7 @@ void TRKServices::createOTServicesPeacock(TGeoVolume* motherVolume) // geometry of service "disk" for OT barrel double rMinOTbarrelServices = 45.0; // cm, radius of first OT barrel layer double rMaxOTbarrelServices = 78.0; // cm, radius of last OT barrel layer - double zOTbarrelServices = 132.0; // cm, approximate position of OT services in z + double zOTbarrelServices = 142.0; // cm, approximate position of OT services in z // geometry of service "tubes" for OT barrel float rMinOuterBarrelTubeServices = rMaxOTbarrelServices; // cm, IA, May 11, 2026: temporary radius (?) diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKSimulationLinkDef.h b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/TRKSimulationLinkDef.h similarity index 63% rename from Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKSimulationLinkDef.h rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/TRKSimulationLinkDef.h index 282fc72becc52..fa929c590adc2 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKSimulationLinkDef.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/TRKSimulationLinkDef.h @@ -15,9 +15,6 @@ #pragma link off all classes; #pragma link off all functions; -#pragma link C++ class o2::trk::Hit + ; -#pragma link C++ class std::vector < o2::trk::Hit> + ; - #pragma link C++ class o2::trk::TRKCylindricalLayer + ; #pragma link C++ class o2::trk::TRKSegmentedLayer + ; #pragma link C++ class o2::trk::TRKMLLayer + ; @@ -26,12 +23,4 @@ #pragma link C++ class o2::trk::TRKServices + ; #pragma link C++ class o2::trk::Detector + ; #pragma link C++ class o2::base::DetImpl < o2::trk::Detector> + ; -#pragma link C++ class o2::trk::Digitizer + ; -#pragma link C++ class o2::trk::ChipSimResponse + ; - -#pragma link C++ class o2::trk::DPLDigitizerParam < o2::detectors::DetID::TRK> + ; -#pragma link C++ class o2::trk::DPLDigitizerParam < o2::detectors::DetID::FT3> + ; -#pragma link C++ class o2::conf::ConfigurableParamHelper < o2::trk::DPLDigitizerParam < o2::detectors::DetID::TRK>> + ; -#pragma link C++ class o2::conf::ConfigurableParamHelper < o2::trk::DPLDigitizerParam < o2::detectors::DetID::FT3>> + ; - #endif diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/VDGeometryBuilder.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/VDGeometryBuilder.cxx similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/simulation/src/VDGeometryBuilder.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/VDGeometryBuilder.cxx diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/VDLayer.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/VDLayer.cxx similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/simulation/src/VDLayer.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/TRK/simulation/src/VDLayer.cxx diff --git a/Detectors/Upgrades/ALICE3/TRK/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRKFT3/common/CMakeLists.txt similarity index 92% rename from Detectors/Upgrades/ALICE3/TRK/CMakeLists.txt rename to Detectors/Upgrades/ALICE3/TRKFT3/common/CMakeLists.txt index 6e3437c9d841b..6cd471f6f74de 100644 --- a/Detectors/Upgrades/ALICE3/TRK/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/CMakeLists.txt @@ -9,8 +9,6 @@ # granted to it by virtue of its status as an Intergovernmental Organization # or submit itself to any jurisdiction. -add_subdirectory(base) -add_subdirectory(macros) add_subdirectory(simulation) add_subdirectory(reconstruction) add_subdirectory(workflow) diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/CMakeLists.txt similarity index 96% rename from Detectors/Upgrades/ALICE3/TRK/reconstruction/CMakeLists.txt rename to Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/CMakeLists.txt index 45ce53ba7c3a3..fab32edc7c819 100644 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/CMakeLists.txt @@ -20,7 +20,7 @@ o2_add_library(TRKReconstruction PUBLIC_LINK_LIBRARIES Microsoft.GSL::GSL O2::DataFormatsITSMFT - O2::DataFormatsTRK + O2::DataFormatsTRKFT3 O2::SimulationDataFormat O2::TRKBase nlohmann_json::nlohmann_json diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/Clusterer.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/include/TRKReconstruction/Clusterer.h similarity index 84% rename from Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/Clusterer.h rename to Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/include/TRKReconstruction/Clusterer.h index 3d30eb5068efe..0e709476d09fd 100644 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/Clusterer.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/include/TRKReconstruction/Clusterer.h @@ -19,11 +19,11 @@ // | *| #define _ALLOW_DIAGONAL_TRK_CLUSTERS_ -#include "DataFormatsITSMFT/Digit.h" -#include "DataFormatsITSMFT/ROFRecord.h" +#include "DataFormatsTRKFT3/Digit.h" #include "DataFormatsITSMFT/ClusterPattern.h" -#include "DataFormatsTRK/Cluster.h" -#include "DataFormatsTRK/ROFRecord.h" +#include "DataFormatsTRKFT3/Cluster.h" +#include "DataFormatsTRKFT3/ROFRecord.h" +#include "DetectorsCommonDataFormats/DetID.h" #include "SimulationDataFormat/ConstMCTruthContainer.h" #include "SimulationDataFormat/MCCompLabel.h" #include "SimulationDataFormat/MCTruthContainer.h" @@ -41,15 +41,18 @@ namespace o2::trk class GeometryTGeo; +template class Clusterer { + static_assert(DetID == o2::detectors::DetID::TRK || DetID == o2::detectors::DetID::FT3, "only TRK and FT3 clusterers are supported"); + public: static constexpr int MaxLabels = 10; static constexpr int MaxHugeClusWarn = 5; - using Digit = o2::itsmft::Digit; - using DigROFRecord = o2::itsmft::ROFRecord; - using DigMC2ROFRecord = o2::itsmft::MC2ROFRecord; + using Digit = o2::trkft3::Digit; + using DigROFRecord = o2::trkft3::ROFRecord; + using ClusterType = o2::trkft3::Cluster; using ClusterTruth = o2::dataformats::MCTruthContainer; using ConstDigitTruth = o2::dataformats::ConstMCTruthContainerView; using Label = o2::MCCompLabel; @@ -87,7 +90,7 @@ class Clusterer //---------------------------------------------- struct ClustererThread { - Clusterer* parent = nullptr; + Clusterer* parent = nullptr; // column buffers (pre-cluster state); extra sentinel entries at [0] and [size-1] int* column1 = nullptr; int* column2 = nullptr; @@ -106,7 +109,7 @@ class Clusterer std::vector> pixArrBuff; ///< (row,col) pixel buffer for pattern // per-thread output (accumulated, then merged back by caller) - std::vector clusters; + std::vector clusters; std::vector patterns; ClusterTruth labels; @@ -144,19 +147,19 @@ class Clusterer const ConstDigitTruth* labelsDigPtr, ClusterTruth* labelsClusPtr, GeometryTGeo* geom); void processChip(gsl::span digits, int chipFirst, int chipN, - std::vector* clustersOut, std::vector* patternsOut, + std::vector* clustersOut, std::vector* patternsOut, const ConstDigitTruth* labelsDigPtr, ClusterTruth* labelsClusPtr, GeometryTGeo* geom); void streamCluster(const BBox& bbox, const std::vector>& pixbuf, uint32_t totalCharge, bool doLabels, int nlab, - uint16_t chipID, int subDetID, int layer, int disk); + uint16_t chipID, int subDetID, int layer); ~ClustererThread() { delete[] column1; delete[] column2; } - explicit ClustererThread(Clusterer* par = nullptr) : parent(par) {} + explicit ClustererThread(Clusterer* par = nullptr) : parent(par) {} ClustererThread(const ClustererThread&) = delete; ClustererThread& operator=(const ClustererThread&) = delete; }; @@ -164,15 +167,13 @@ class Clusterer virtual void process(gsl::span digits, gsl::span digitROFs, - std::vector& clusters, + std::vector& clusters, std::vector& patterns, - std::vector& clusterROFs, + std::vector& clusterROFs, const ConstDigitTruth* digitLabels = nullptr, - ClusterTruth* clusterLabels = nullptr, - gsl::span digMC2ROFs = {}, - std::vector* clusterMC2ROFs = nullptr); + ClusterTruth* clusterLabels = nullptr); - static o2::math_utils::Point3D getClusterLocalCoordinates(const Cluster& cluster, const uint8_t* patt, + static o2::math_utils::Point3D getClusterLocalCoordinates(const ClusterType& cluster, const uint8_t* patt, float yPlaneMLOT = 0.f) noexcept; protected: @@ -181,6 +182,9 @@ class Clusterer std::vector mSortIdx; ///< reusable per-ROF sort buffer }; +using TRKClusterer = Clusterer; +using FT3Clusterer = Clusterer; + } // namespace o2::trk #endif diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/ClustererACTS.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/include/TRKReconstruction/ClustererACTS.h similarity index 76% rename from Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/ClustererACTS.h rename to Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/include/TRKReconstruction/ClustererACTS.h index 37a148aa78afb..f207a6dc0e24c 100644 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/ClustererACTS.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/include/TRKReconstruction/ClustererACTS.h @@ -26,18 +26,16 @@ namespace o2::trk class GeometryTGeo; -class ClustererACTS : public Clusterer +class ClustererACTS : public TRKClusterer { public: void process(gsl::span digits, gsl::span digitROFs, - std::vector& clusters, + std::vector& clusters, std::vector& patterns, - std::vector& clusterROFs, + std::vector& clusterROFs, const ConstDigitTruth* digitLabels = nullptr, - ClusterTruth* clusterLabels = nullptr, - gsl::span digMC2ROFs = {}, - std::vector* clusterMC2ROFs = nullptr) override; + ClusterTruth* clusterLabels = nullptr) override; private: }; diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/Clusterer.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/src/Clusterer.cxx similarity index 80% rename from Detectors/Upgrades/ALICE3/TRK/reconstruction/src/Clusterer.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/src/Clusterer.cxx index d60d6900657ba..0bd64be5efbc4 100644 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/Clusterer.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/src/Clusterer.cxx @@ -23,8 +23,9 @@ namespace o2::trk { //__________________________________________________ -o2::math_utils::Point3D Clusterer::getClusterLocalCoordinates(const Cluster& cluster, const uint8_t* patt, - float yPlaneMLOT) noexcept +template +o2::math_utils::Point3D Clusterer::getClusterLocalCoordinates(const typename Clusterer::ClusterType& cluster, const uint8_t* patt, + float yPlaneMLOT) noexcept { const uint8_t rowSpan = *patt++; const uint8_t colSpan = *patt++; @@ -49,7 +50,7 @@ o2::math_utils::Point3D Clusterer::getClusterLocalCoordinates(const Clust float x{0.f}, y{0.f}, z{0.f}; SegmentationChip::detectorToLocalUnchecked(cluster.row, cluster.col, x, z, - cluster.subDetID, cluster.layer, cluster.disk); + cluster.subDetID, cluster.layer, cluster.layer); const float pitchRow = (cluster.subDetID == 0) ? SegmentationChip::PitchRowVD : SegmentationChip::PitchRowMLOT; const float pitchCol = (cluster.subDetID == 0) ? SegmentationChip::PitchColVD : SegmentationChip::PitchColMLOT; @@ -68,15 +69,14 @@ o2::math_utils::Point3D Clusterer::getClusterLocalCoordinates(const Clust } //__________________________________________________ -void Clusterer::process(gsl::span digits, - gsl::span digitROFs, - std::vector& clusters, - std::vector& patterns, - std::vector& clusterROFs, - const ConstDigitTruth* digitLabels, - ClusterTruth* clusterLabels, - gsl::span digMC2ROFs, - std::vector* clusterMC2ROFs) +template +void Clusterer::process(gsl::span digits, + gsl::span digitROFs, + std::vector& clusters, + std::vector& patterns, + std::vector& clusterROFs, + const ConstDigitTruth* digitLabels, + ClusterTruth* clusterLabels) { if (!mThread) { mThread = std::make_unique(this); @@ -127,23 +127,17 @@ void Clusterer::process(gsl::span digits, clusterROFs.emplace_back(inROF.getBCData(), inROF.getROFrame(), outFirst, static_cast(clusters.size()) - outFirst); } - - if (clusterMC2ROFs && !digMC2ROFs.empty()) { - clusterMC2ROFs->reserve(clusterMC2ROFs->size() + digMC2ROFs.size()); - for (const auto& in : digMC2ROFs) { - clusterMC2ROFs->emplace_back(in.eventRecordID, in.rofRecordID, in.minROF, in.maxROF); - } - } } //__________________________________________________ -void Clusterer::ClustererThread::processChip(gsl::span digits, - int chipFirst, int chipN, - std::vector* clustersOut, - std::vector* patternsOut, - const ConstDigitTruth* labelsDigPtr, - ClusterTruth* labelsClusPtr, - GeometryTGeo* geom) +template +void Clusterer::ClustererThread::processChip(gsl::span digits, + int chipFirst, int chipN, + std::vector* clustersOut, + std::vector* patternsOut, + const ConstDigitTruth* labelsDigPtr, + ClusterTruth* labelsClusPtr, + GeometryTGeo* geom) { // chipFirst and chipN are relative to mSortIdx (i.e. mSortIdx[chipFirst..chipFirst+chipN-1] // are the global digit indices for this chip, already sorted by col then row). @@ -176,7 +170,8 @@ void Clusterer::ClustererThread::processChip(gsl::span digits, } //__________________________________________________ -void Clusterer::ClustererThread::initChip(gsl::span digits, uint32_t first, GeometryTGeo* geom) +template +void Clusterer::ClustererThread::initChip(gsl::span digits, uint32_t first, GeometryTGeo* geom) { const uint16_t chipID = digits[first].getChipIndex(); @@ -213,7 +208,8 @@ void Clusterer::ClustererThread::initChip(gsl::span digits, uint32_ } //__________________________________________________ -void Clusterer::ClustererThread::updateChip(gsl::span digits, uint32_t ip) +template +void Clusterer::ClustererThread::updateChip(gsl::span digits, uint32_t ip) { const auto& pix = digits[ip]; uint16_t row = pix.getRow(); @@ -268,10 +264,11 @@ void Clusterer::ClustererThread::updateChip(gsl::span digits, uint3 } //__________________________________________________ -void Clusterer::ClustererThread::finishChip(gsl::span digits, - const ConstDigitTruth* labelsDigPtr, - ClusterTruth* labelsClusPtr, - GeometryTGeo* geom) +template +void Clusterer::ClustererThread::finishChip(gsl::span digits, + const ConstDigitTruth* labelsDigPtr, + ClusterTruth* labelsClusPtr, + GeometryTGeo* geom) { const uint16_t chipID = digits[pixels[0].second].getChipIndex(); @@ -314,16 +311,15 @@ void Clusterer::ClustererThread::finishChip(gsl::span digits, } // Determine geometry info - int subDetID = -1, layer = -1, disk = -1; + int subDetID = -1, layer = -1; if (geom) { subDetID = geom->getSubDetID(chipID); layer = geom->getLayer(chipID); - disk = geom->getDisk(chipID); } const bool doLabels = (labelsClusPtr != nullptr); if (bbox.isAcceptableSize()) { - streamCluster(bbox, pixArrBuff, totalCharge, doLabels, nlab, chipID, subDetID, layer, disk); + streamCluster(bbox, pixArrBuff, totalCharge, doLabels, nlab, chipID, subDetID, layer); } else { // Huge cluster: split into MaxRowSpan x MaxColSpan tiles (same as ITS3) auto warnLeft = MaxHugeClusWarn - parent->mNHugeClus; @@ -349,7 +345,7 @@ void Clusterer::ClustererThread::finishChip(gsl::span digits, } } if (!subPix.empty()) { - streamCluster(bboxT, subPix, subCharge, doLabels, nlab, chipID, subDetID, layer, disk); + streamCluster(bboxT, subPix, subCharge, doLabels, nlab, chipID, subDetID, layer); } bboxT.rowMin = bboxT.rowMax + 1; } while (bboxT.rowMin <= bbox.rowMax); @@ -361,10 +357,11 @@ void Clusterer::ClustererThread::finishChip(gsl::span digits, } //__________________________________________________ -void Clusterer::ClustererThread::finishChipSingleHitFast(gsl::span digits, uint32_t hit, - const ConstDigitTruth* labelsDigPtr, - ClusterTruth* labelsClusPtr, - GeometryTGeo* geom) +template +void Clusterer::ClustererThread::finishChipSingleHitFast(gsl::span digits, uint32_t hit, + const ConstDigitTruth* labelsDigPtr, + ClusterTruth* labelsClusPtr, + GeometryTGeo* geom) { const auto& d = digits[hit]; const uint16_t chipID = d.getChipIndex(); @@ -385,7 +382,7 @@ void Clusterer::ClustererThread::finishChipSingleHitFast(gsl::span patterns.emplace_back(1); patterns.emplace_back(0x80); - Cluster cluster; + ClusterType cluster; cluster.chipID = chipID; cluster.row = row; cluster.col = col; @@ -393,17 +390,17 @@ void Clusterer::ClustererThread::finishChipSingleHitFast(gsl::span if (geom) { cluster.subDetID = geom->getSubDetID(chipID); cluster.layer = geom->getLayer(chipID); - cluster.disk = geom->getDisk(chipID); } clusters.emplace_back(cluster); } //__________________________________________________ -void Clusterer::ClustererThread::streamCluster(const BBox& bbox, - const std::vector>& pixbuf, - uint32_t totalCharge, - bool doLabels, int nlab, - uint16_t chipID, int subDetID, int layer, int disk) +template +void Clusterer::ClustererThread::streamCluster(const BBox& bbox, + const std::vector>& pixbuf, + uint32_t totalCharge, + bool doLabels, int nlab, + uint16_t chipID, int subDetID, int layer) { if (doLabels) { const auto cnt = static_cast(clusters.size()); @@ -427,19 +424,19 @@ void Clusterer::ClustererThread::streamCluster(const BBox& bbox, int nBytes = (rowSpanW * colSpanW + 7) / 8; patterns.insert(patterns.end(), patt.begin(), patt.begin() + nBytes); - Cluster cluster; + ClusterType cluster; cluster.chipID = chipID; cluster.row = bbox.rowMin; cluster.col = bbox.colMin; cluster.size = static_cast(pixbuf.size()); cluster.subDetID = static_cast(subDetID); cluster.layer = static_cast(layer); - cluster.disk = static_cast(disk); clusters.emplace_back(cluster); } //__________________________________________________ -void Clusterer::ClustererThread::fetchMCLabels(uint32_t digID, const ConstDigitTruth* labelsDig, int& nfilled) +template +void Clusterer::ClustererThread::fetchMCLabels(uint32_t digID, const ConstDigitTruth* labelsDig, int& nfilled) { if (nfilled >= MaxLabels) { return; @@ -462,4 +459,7 @@ void Clusterer::ClustererThread::fetchMCLabels(uint32_t digID, const ConstDigitT } } +template class Clusterer; +template class Clusterer; + } // namespace o2::trk diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/ClustererACTS.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/src/ClustererACTS.cxx similarity index 94% rename from Detectors/Upgrades/ALICE3/TRK/reconstruction/src/ClustererACTS.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/src/ClustererACTS.cxx index 30ab503b7e250..86ac9f508a042 100644 --- a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/ClustererACTS.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/reconstruction/src/ClustererACTS.cxx @@ -158,13 +158,11 @@ Cluster2D gencluster(int x0, int y0, int x1, int y1, RNG& rng, //__________________________________________________ void ClustererACTS::process(gsl::span digits, gsl::span digitROFs, - std::vector& clusters, + std::vector& clusters, std::vector& patterns, - std::vector& clusterROFs, + std::vector& clusterROFs, const ConstDigitTruth* digitLabels, - ClusterTruth* clusterLabels, - gsl::span digMC2ROFs, - std::vector* clusterMC2ROFs) + ClusterTruth* clusterLabels) { if (!mThread) { mThread = std::make_unique(this); @@ -326,7 +324,7 @@ void ClustererACTS::process(gsl::span digits, } // Create O2 cluster for this tile - o2::trk::Cluster cluster; + o2::trkft3::TRKCluster cluster; cluster.chipID = chipID; cluster.row = tileRowMin; cluster.col = tileColMin; @@ -334,7 +332,6 @@ void ClustererACTS::process(gsl::span digits, if (geom) { cluster.subDetID = static_cast(geom->getSubDetID(chipID)); cluster.layer = static_cast(geom->getLayer(chipID)); - cluster.disk = static_cast(geom->getDisk(chipID)); } clusters.emplace_back(cluster); } @@ -367,7 +364,7 @@ void ClustererACTS::process(gsl::span digits, } // Create O2 cluster - o2::trk::Cluster cluster; + o2::trkft3::TRKCluster cluster; cluster.chipID = chipID; cluster.row = rowMin; cluster.col = colMin; @@ -375,7 +372,6 @@ void ClustererACTS::process(gsl::span digits, if (geom) { cluster.subDetID = static_cast(geom->getSubDetID(chipID)); cluster.layer = static_cast(geom->getLayer(chipID)); - cluster.disk = static_cast(geom->getDisk(chipID)); } clusters.emplace_back(cluster); } @@ -386,11 +382,4 @@ void ClustererACTS::process(gsl::span digits, clusterROFs.emplace_back(inROF.getBCData(), inROF.getROFrame(), outFirst, static_cast(clusters.size()) - outFirst); } - - // if (clusterMC2ROFs && !digMC2ROFs.empty()) { - // clusterMC2ROFs->reserve(clusterMC2ROFs->size() + digMC2ROFs.size()); - // for (const auto& in : digMC2ROFs) { - // clusterMC2ROFs->emplace_back(in.eventRecordID, in.rofRecordID, in.minROF, in.maxROF); - // } - // } } diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/CMakeLists.txt new file mode 100644 index 0000000000000..ce9b79217997e --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/CMakeLists.txt @@ -0,0 +1,31 @@ +# Copyright 2019-2026 CERN and copyright holders of ALICE O2. +# See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +# All rights not expressly granted are reserved. +# +# This software is distributed under the terms of the GNU General Public +# License v3 (GPL Version 3), copied verbatim in the file "COPYING". +# +# In applying this license CERN does not waive the privileges and immunities +# granted to it by virtue of its status as an Intergovernmental Organization +# or submit itself to any jurisdiction. + +o2_add_library(TRKFT3Simulation + SOURCES src/ChipDigitsContainer.cxx + src/ChipSimResponse.cxx + src/DigiParams.cxx + src/Digitizer.cxx + src/DPLDigitizerParam.cxx + PUBLIC_LINK_LIBRARIES O2::TRKBase + O2::FT3Base + O2::DataFormatsTRKFT3 + O2::ITSMFTSimulation + O2::DetectorsRaw + O2::SimulationDataFormat) + +o2_target_root_dictionary(TRKFT3Simulation + HEADERS include/TRKFT3Simulation/ChipDigitsContainer.h + include/TRKFT3Simulation/ChipSimResponse.h + include/TRKFT3Simulation/DigiParams.h + include/TRKFT3Simulation/Digitizer.h + include/TRKFT3Simulation/DPLDigitizerParam.h + LINKDEF src/TRKFT3SimulationLinkDef.h) diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/include/TRKFT3Simulation/ChipDigitsContainer.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/include/TRKFT3Simulation/ChipDigitsContainer.h new file mode 100644 index 0000000000000..10c55e6163846 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/include/TRKFT3Simulation/ChipDigitsContainer.h @@ -0,0 +1,92 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef ALICEO2_TRK_CHIPDIGITSCONTAINER_ +#define ALICEO2_TRK_CHIPDIGITSCONTAINER_ + +#include "ITSMFTBase/SegmentationAlpide.h" +#include "ITSMFTSimulation/ChipDigitsContainer.h" +#include "TRKBase/SegmentationChip.h" +#include "TRKBase/Specs.h" +#include "TRKFT3Simulation/DigiParams.h" +#include +#include + +namespace o2::trkft3 +{ + +class ChipDigitsContainer : public o2::itsmft::ChipDigitsContainer +{ + public: + explicit ChipDigitsContainer(UShort_t idx = 0); + + using Segmentation = o2::trk::SegmentationChip; + + /// Get global ordering key made of readout frame, column and row + static ULong64_t getOrderingKey(UInt_t roframe, UShort_t row, UShort_t col) + { + return (static_cast(roframe) << (8 * sizeof(UInt_t))) + (static_cast(col) << (8 * sizeof(Short_t))) + row; + } + + /// Adds noise digits, deleted the one using the itsmft::DigiParams interface + void addNoise(UInt_t rofMin, UInt_t rofMax, const o2::itsmft::DigiParams* params, int maxRows = o2::itsmft::SegmentationAlpide::NRows, int maxCols = o2::itsmft::SegmentationAlpide::NCols) = delete; + template + void addNoise(UInt_t rofMin, UInt_t rofMax, const o2::trkft3::DigiParams* params, int subDetID, int layer); + + ClassDefNV(ChipDigitsContainer, 1); +}; + +} // namespace o2::trkft3 + +template +void o2::trkft3::ChipDigitsContainer::addNoise(UInt_t rofMin, UInt_t rofMax, const o2::trkft3::DigiParams* params, int subDetID, int layer) +{ + UInt_t row = 0; + UInt_t col = 0; + Int_t nhits = 0; + float mean = 0.f; + int nel = 0; + int maxRows = 0; + int maxCols = 0; + + if (subDetID == 0) { + maxRows = o2::trk::constants::VD::petal::layer::nRows[layer]; + maxCols = o2::trk::constants::VD::petal::layer::nCols; + } else { + maxRows = o2::trk::constants::moduleMLOT::chip::nRows; + maxCols = o2::trk::constants::moduleMLOT::chip::nCols; + } + mean = params->getNoisePerPixel() * maxRows * maxCols; + nel = static_cast(params->getChargeThreshold() * 1.1); + + LOG(debug) << "Adding noise for chip " << mChipIndex << " with mean " << mean << " and charge " << nel; + + for (UInt_t rof = rofMin; rof <= rofMax; rof++) { + nhits = gRandom->Poisson(mean); + for (Int_t i = 0; i < nhits; ++i) { + row = gRandom->Integer(maxRows); + col = gRandom->Integer(maxCols); + LOG(debug) << "Generated noise hit at ROF " << rof << ", row " << row << ", col " << col; + if (mNoiseMap && mNoiseMap->isNoisy(mChipIndex, row, col)) { + continue; + } + if (mDeadChanMap && mDeadChanMap->isNoisy(mChipIndex, row, col)) { + continue; + } + auto key = getOrderingKey(rof, row, col); + if (!findDigit(key)) { + addDigit(key, rof, row, col, nel, o2::MCCompLabel(true)); + } + } + } +} + +#endif // ALICEO2_TRK_CHIPDIGITSCONTAINER_ diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/ChipSimResponse.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/include/TRKFT3Simulation/ChipSimResponse.h similarity index 96% rename from Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/ChipSimResponse.h rename to Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/include/TRKFT3Simulation/ChipSimResponse.h index 29147997f66bf..99f966f0c608a 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/ChipSimResponse.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/include/TRKFT3Simulation/ChipSimResponse.h @@ -16,7 +16,7 @@ namespace o2 { -namespace trk +namespace trkft3 { class ChipSimResponse : public o2::itsmft::AlpideSimResponse @@ -31,7 +31,7 @@ class ChipSimResponse : public o2::itsmft::AlpideSimResponse ClassDef(ChipSimResponse, 1); }; -} // namespace trk +} // namespace trkft3 } // namespace o2 #endif // ALICEO2_TRKSIMULATION_CHIPSIMRESPONSE_H diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/DPLDigitizerParam.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/include/TRKFT3Simulation/DPLDigitizerParam.h similarity index 98% rename from Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/DPLDigitizerParam.h rename to Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/include/TRKFT3Simulation/DPLDigitizerParam.h index de839b27aefee..f4b37142b6019 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/DPLDigitizerParam.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/include/TRKFT3Simulation/DPLDigitizerParam.h @@ -19,7 +19,7 @@ namespace o2 { -namespace trk +namespace trkft3 { template struct DPLDigitizerParam : public o2::conf::ConfigurableParamHelper> { @@ -63,7 +63,7 @@ struct DPLDigitizerParam : public o2::conf::ConfigurableParamHelper DPLDigitizerParam DPLDigitizerParam::sInstance; -} // namespace trk +} // namespace trkft3 } // namespace o2 #endif diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/DigiParams.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/include/TRKFT3Simulation/DigiParams.h similarity index 72% rename from Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/DigiParams.h rename to Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/include/TRKFT3Simulation/DigiParams.h index d7d1ea28bfcf7..004bf6fb40759 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/DigiParams.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/include/TRKFT3Simulation/DigiParams.h @@ -16,13 +16,14 @@ #define ALICEO2_TRK_DIGIPARAMS_H #include +#include +#include #include +#include "DetectorsCommonDataFormats/DetID.h" #include "ITSMFTSimulation/AlpideSignalTrapezoid.h" #include "ITSMFTSimulation/AlpideSimResponse.h" #include "TRKBase/AlmiraParam.h" -#include "TRKBase/TRKBaseParam.h" -#include "TRKBase/GeometryTGeo.h" //////////////////////////////////////////////////////////// // // @@ -36,20 +37,41 @@ namespace o2 { -namespace trk +namespace trkft3 { class ChipSimResponse; +namespace detail +{ +template +struct DigiParamsLayerTraits; + +template <> +struct DigiParamsLayerTraits { + static constexpr size_t MaxLayers = o2::trk::AlmiraParam::getNLayers(); +}; + +template <> +struct DigiParamsLayerTraits { + static constexpr size_t MaxLayers = 20; // two FT3 sides with the default 10 layers per side +}; +} // namespace detail + +template class DigiParams { + static_assert(DetIDV == o2::detectors::DetID::TRK || DetIDV == o2::detectors::DetID::FT3, "only TRK and FT3 digit parameters are supported"); using SignalShape = o2::itsmft::AlpideSignalTrapezoid; + static constexpr size_t MaxLayers = detail::DigiParamsLayerTraits::MaxLayers; public: DigiParams(); ~DigiParams() = default; + static constexpr size_t getMaxLayers() { return MaxLayers; } + void setNoisePerPixel(float v) { mNoisePerPixel = v; } float getNoisePerPixel() const { return mNoisePerPixel; } @@ -92,7 +114,7 @@ class DigiParams bool isTimeOffsetSet() const { return mTimeOffset > -infTime; } - const o2::trk::ChipSimResponse* getResponse() const { return mResponse.get(); } + const o2::trkft3::ChipSimResponse* getResponse() const { return mResponse.get(); } void setResponse(const o2::itsmft::AlpideSimResponse*); const SignalShape& getSignalShape() const { return mSignalShape; } @@ -115,22 +137,25 @@ class DigiParams float mIBVbb = 0.0; ///< back bias absolute value for ITS Inner Barrel (in Volt) float mOBVbb = 0.0; ///< back bias absolute value for ITS Outter Barrel (in Volt) - std::array mROFrameLayerLengthInBC; ///< staggering ROF length in BC for continuous mode per layer - std::array mROFrameLayerBiasInBC; ///< staggering ROF bias in BC for continuous mode per layer - std::array mROFrameLayerLength; ///< staggering ROF length in ns for continuous mode per layer - std::array mStrobeLayerLength; ///< staggering strobe length in ns per layer - std::array mStrobeLayerDelay; ///< staggering strobe delay in ns per layer + std::array mROFrameLayerLengthInBC; ///< staggering ROF length in BC for continuous mode per layer + std::array mROFrameLayerBiasInBC; ///< staggering ROF bias in BC for continuous mode per layer + std::array mROFrameLayerLength; ///< staggering ROF length in ns for continuous mode per layer + std::array mStrobeLayerLength; ///< staggering strobe length in ns per layer + std::array mStrobeLayerDelay; ///< staggering strobe delay in ns per layer o2::itsmft::AlpideSignalTrapezoid mSignalShape; ///< signal timeshape parameterization - std::unique_ptr mResponse; //!< pointer on external response + std::unique_ptr mResponse; //!< pointer on external response // auxiliary precalculated parameters - std::array mROFrameLayerLengthInv; ///< inverse length of RO frame in ns per layer + std::array mROFrameLayerLengthInv; ///< inverse length of RO frame in ns per layer // ClassDef(DigiParams, 2); }; -} // namespace trk + +using TRKDigiParams = DigiParams; +using FT3DigiParams = DigiParams; +} // namespace trkft3 } // namespace o2 #endif diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Digitizer.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/include/TRKFT3Simulation/Digitizer.h similarity index 59% rename from Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Digitizer.h rename to Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/include/TRKFT3Simulation/Digitizer.h index 5910fc98134aa..22c61352c03ee 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Digitizer.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/include/TRKFT3Simulation/Digitizer.h @@ -10,52 +10,57 @@ // or submit itself to any jurisdiction. /// \file Digitizer.h -/// \brief Definition of the TRK digitizer -#ifndef ALICEO2_TRK_DIGITIZER_H -#define ALICEO2_TRK_DIGITIZER_H +/// \brief Definition of the TRK/FT3 digitizer +#ifndef ALICEO2_TRKFT3_DIGITIZER_H +#define ALICEO2_TRKFT3_DIGITIZER_H #include #include #include +#include #include "Rtypes.h" // for Digitizer::Class #include "TObject.h" // for TObject -#include "TRKSimulation/ChipSimResponse.h" -#include "TRKSimulation/ChipDigitsContainer.h" +#include "TRKFT3Simulation/ChipSimResponse.h" +#include "TRKFT3Simulation/ChipDigitsContainer.h" -#include "TRKSimulation/DigiParams.h" -#include "TRKSimulation/Hit.h" +#include "TRKFT3Simulation/DigiParams.h" +#include "DataFormatsTRKFT3/Hit.h" +#include "DetectorsCommonDataFormats/DetID.h" +#include "FT3Base/GeometryTGeo.h" #include "TRKBase/GeometryTGeo.h" -#include "DataFormatsITSMFT/Digit.h" -#include "DataFormatsITSMFT/ROFRecord.h" +#include "DataFormatsTRKFT3/Digit.h" +#include "DataFormatsTRKFT3/ROFRecord.h" #include "CommonDataFormat/InteractionRecord.h" #include "SimulationDataFormat/MCCompLabel.h" #include "SimulationDataFormat/MCTruthContainer.h" -#endif -namespace o2::trk +namespace o2::trkft3 { +template class Digitizer { + static_assert(DetID == o2::detectors::DetID::TRK || DetID == o2::detectors::DetID::FT3, "only TRK and FT3 digitizers are supported"); + using GeometryTGeo = std::conditional_t; using ExtraDig = std::vector; ///< container for extra contributions to PreDigits public: - void setDigits(std::vector* dig) { mDigits = dig; } + void setDigits(std::vector* dig) { mDigits = dig; } void setMCLabels(o2::dataformats::MCTruthContainer* mclb) { mMCLabels = mclb; } - void setROFRecords(std::vector* rec) { mROFRecords = rec; } + void setROFRecords(std::vector* rec) { mROFRecords = rec; } void setResponseName(const std::string& name) { mRespName = name; } - o2::trk::DigiParams& getParams() { return (o2::trk::DigiParams&)mParams; } - const o2::trk::DigiParams& getParams() const { return mParams; } + o2::trkft3::DigiParams& getParams() { return mParams; } + const o2::trkft3::DigiParams& getParams() const { return mParams; } void init(); - const o2::trk::ChipSimResponse* getChipResponse(int chipID); + const o2::trkft3::ChipSimResponse* getChipResponse(int chipID); /// Steer conversion of hits to digits - void process(const std::vector* hits, int evID, int srcID, int layer); + void process(const std::vector* hits, int evID, int srcID, int layer); void setEventTime(const o2::InteractionTimeRecord& irt, int layer); void fillOutputContainer(uint32_t maxFrame, int layer); @@ -65,14 +70,17 @@ class Digitizer mROFrameMin = 0; mROFrameMax = 0; mNewROFrame = 0; - mIsBeforeFirstRO = false; + mROFsWrtFirstRO = 0; mExtraBuff.clear(); } - const o2::trk::DigiParams& getDigitParams() const { return mParams; } + const o2::trkft3::DigiParams& getDigitParams() const { return mParams; } - // provide the common trk::GeometryTGeo to access matrices and segmentation - void setGeometry(const o2::trk::GeometryTGeo* gm) { mGeometry = gm; } + void setGeometry(const GeometryTGeo* gm) + { + LOG(info) << "trkft3::Digitizer set geom"; + mGeometry = gm; + } uint32_t getEventROFrameMin() const { return mEventROFrameMin; } uint32_t getEventROFrameMax() const { return mEventROFrameMax; } @@ -85,8 +93,8 @@ class Digitizer void setDeadChannelsMap(const o2::itsmft::NoiseMap* mp) { mDeadChanMap = mp; } private: - void processHit(const o2::trk::Hit& hit, uint32_t& maxFr, int evID, int srcID, int rofLayer); - void registerDigits(o2::trk::ChipDigitsContainer& chip, uint32_t roFrame, float tInROF, int nROF, + void processHit(const o2::trkft3::Hit& hit, uint32_t& maxFr, int evID, int srcID, int rofLayer); + void registerDigits(o2::trkft3::ChipDigitsContainer& chip, uint32_t roFrame, float tInROF, int nROF, uint16_t row, uint16_t col, int nEle, o2::MCCompLabel& lbl, int layer); ExtraDig* getExtraDigBuffer(uint32_t roFrame) @@ -108,9 +116,9 @@ class Digitizer int getNCols(int subDetID, int layer) { if (subDetID == 0) { // VD - return constants::VD::petal::layer::nCols; - } else if (subDetID == 1) { // ML/OT: the smallest element is a chip of 470 rows and 640 cols - return constants::moduleMLOT::chip::nCols; + return o2::trk::constants::VD::petal::layer::nCols; + } else if (subDetID == 1 || subDetID == 2) { // ML/OT: the smallest element is a chip of 470 rows and 640 cols + return o2::trk::constants::moduleMLOT::chip::nCols; } return 0; } @@ -122,16 +130,34 @@ class Digitizer int getNRows(int subDetID, int layer) { if (subDetID == 0) { // VD - return constants::VD::petal::layer::nRows[layer]; - } else if (subDetID == 1) { // ML/OT - return constants::moduleMLOT::chip::nRows; + return o2::trk::constants::VD::petal::layer::nRows[layer]; + } else if (subDetID == 1 || subDetID == 2) { // ML/OT + return o2::trk::constants::moduleMLOT::chip::nRows; } return 0; } + int getROFLayer(int chipID) const + { + if constexpr (DetID == o2::detectors::DetID::TRK) { + return mGeometry->getLayerTRK(chipID); + } else { + return mGeometry->getLayer(chipID); + } + } + + int getDisk(int chipID) const + { + if constexpr (DetID == o2::detectors::DetID::TRK) { + return mGeometry->getDisk(chipID); + } else { + return -1; + } + } + static constexpr float sec2ns = 1e9; - o2::trk::DigiParams mParams; ///< digitization parameters + o2::trkft3::DigiParams mParams; ///< digitization parameters o2::InteractionTimeRecord mEventTime; ///< global event time and interaction record o2::InteractionRecord mIRFirstSampledTF; ///< IR of the 1st sampled IR, noise-only ROFs will be inserted till this IR only double mCollisionTimeWrtROF{}; @@ -139,16 +165,16 @@ class Digitizer uint32_t mROFrameMax = 0; ///< highest RO frame of current digits uint32_t mNewROFrame = 0; ///< ROFrame corresponding to provided time - bool mIsBeforeFirstRO = false; + int mROFsWrtFirstRO = 0; uint32_t mEventROFrameMin = 0xffffffff; ///< lowest RO frame for processed events (w/o automatic noise ROFs) uint32_t mEventROFrameMax = 0; ///< highest RO frame forfor processed events (w/o automatic noise ROFs) int mNumberOfChips = 0; - const o2::trk::ChipSimResponse* mChipSimResp = nullptr; // simulated response - const o2::trk::ChipSimResponse* mChipSimRespVD = nullptr; // simulated response for VD chips - const o2::trk::ChipSimResponse* mChipSimRespMLOT = nullptr; // simulated response for ML/OT chips + const o2::trkft3::ChipSimResponse* mChipSimResp = nullptr; // simulated response + const o2::trkft3::ChipSimResponse* mChipSimRespVD = nullptr; // simulated response for VD chips + const o2::trkft3::ChipSimResponse* mChipSimRespMLOT = nullptr; // simulated response for ML/OT chips std::string mRespName; /// APTS or ALICE3, depending on the response to be used @@ -162,16 +188,21 @@ class Digitizer float mSimRespVDScaleDepth{1.f}; // scale depth-local coordinate to response function depth-coordinate float mSimRespMLOTScaleDepth{1.f}; // scale depth-local coordinate to response function depth-coordinate - const o2::trk::GeometryTGeo* mGeometry = nullptr; ///< TRK geometry + const GeometryTGeo* mGeometry = nullptr; ///< TRK or FT3 geometry - std::vector mChips; ///< Array of chips digits containers - std::deque> mExtraBuff; ///< buffer (per roFrame) for extra digits + std::vector mChips; ///< Array of chips digits containers + std::deque> mExtraBuff; ///< buffer (per roFrame) for extra digits - std::vector* mDigits = nullptr; //! output digits - std::vector* mROFRecords = nullptr; //! output ROF records + std::vector* mDigits = nullptr; //! output digits + std::vector* mROFRecords = nullptr; //! output ROF records o2::dataformats::MCTruthContainer* mMCLabels = nullptr; //! output labels const o2::itsmft::NoiseMap* mDeadChanMap = nullptr; const o2::itsmft::NoiseMap* mNoiseMap = nullptr; }; -} // namespace o2::trk +} // namespace o2::trkft3 + +extern template class o2::trkft3::Digitizer; +extern template class o2::trkft3::Digitizer; + +#endif diff --git a/Detectors/ZDC/raw/src/ZDCRawLinkDef.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/ChipDigitsContainer.cxx similarity index 75% rename from Detectors/ZDC/raw/src/ZDCRawLinkDef.h rename to Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/ChipDigitsContainer.cxx index c85dd2d378ccb..8917062923537 100644 --- a/Detectors/ZDC/raw/src/ZDCRawLinkDef.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/ChipDigitsContainer.cxx @@ -9,10 +9,9 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#ifdef __CLING__ +#include "TRKFT3Simulation/ChipDigitsContainer.h" -#pragma link off all globals; -#pragma link off all classes; -#pragma link off all functions; +using namespace o2::trkft3; -#endif +ChipDigitsContainer::ChipDigitsContainer(UShort_t idx) + : o2::itsmft::ChipDigitsContainer(idx) {} diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/ChipSimResponse.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/ChipSimResponse.cxx similarity index 90% rename from Detectors/Upgrades/ALICE3/TRK/simulation/src/ChipSimResponse.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/ChipSimResponse.cxx index 70c4f131b9724..e8a11fb1b15d0 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/ChipSimResponse.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/ChipSimResponse.cxx @@ -9,11 +9,11 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#include "TRKSimulation/ChipSimResponse.h" +#include "TRKFT3Simulation/ChipSimResponse.h" #include #include -using namespace o2::trk; +using namespace o2::trkft3; void ChipSimResponse::initData(int tableNumber, std::string dataPath, const bool quiet) { diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/DPLDigitizerParam.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/DPLDigitizerParam.cxx similarity index 70% rename from Detectors/Upgrades/ALICE3/TRK/simulation/src/DPLDigitizerParam.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/DPLDigitizerParam.cxx index a13f2e58bd3a4..8d7f4d4b7c767 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/DPLDigitizerParam.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/DPLDigitizerParam.cxx @@ -9,15 +9,15 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#include "TRKSimulation/DPLDigitizerParam.h" +#include "TRKFT3Simulation/DPLDigitizerParam.h" namespace o2 { -namespace trk +namespace trkft3 { // this makes sure that the constructor of the parameters is statically called // so that these params are part of the parameter database -static auto& sDigitizerParamITS = o2::trk::DPLDigitizerParam::Instance(); -static auto& sDigitizerParamMFT = o2::trk::DPLDigitizerParam::Instance(); -} // namespace trk +static auto& sDigitizerParamITS = o2::trkft3::DPLDigitizerParam::Instance(); +static auto& sDigitizerParamMFT = o2::trkft3::DPLDigitizerParam::Instance(); +} // namespace trkft3 } // namespace o2 diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/DigiParams.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/DigiParams.cxx similarity index 72% rename from Detectors/Upgrades/ALICE3/TRK/simulation/src/DigiParams.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/DigiParams.cxx index 3558a6a87ce71..fd2acbe45411d 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/DigiParams.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/DigiParams.cxx @@ -14,18 +14,20 @@ #include #include "Framework/Logger.h" -#include "TRKSimulation/DigiParams.h" -#include "TRKSimulation/ChipSimResponse.h" +#include "TRKFT3Simulation/DigiParams.h" +#include "TRKFT3Simulation/ChipSimResponse.h" -using namespace o2::trk; +using namespace o2::trkft3; -DigiParams::DigiParams() +template +DigiParams::DigiParams() { // make sure the defaults are consistent setNSimSteps(mNSimSteps); } -void DigiParams::setROFrameLength(float lNS, int layer) +template +void DigiParams::setROFrameLength(float lNS, int layer) { // set ROFrame length in nanosecongs mROFrameLayerLength[layer] = lNS; @@ -33,14 +35,16 @@ void DigiParams::setROFrameLength(float lNS, int layer) mROFrameLayerLengthInv[layer] = 1. / mROFrameLayerLength[layer]; } -void DigiParams::setNSimSteps(int v) +template +void DigiParams::setNSimSteps(int v) { // set number of sampling steps in silicon mNSimSteps = v > 0 ? v : 1; mNSimStepsInv = 1.f / mNSimSteps; } -void DigiParams::setChargeThreshold(int v, float frac2Account) +template +void DigiParams::setChargeThreshold(int v, float frac2Account) { // set charge threshold for digits creation and its fraction to account // contribution from single hit @@ -55,10 +59,11 @@ void DigiParams::setChargeThreshold(int v, float frac2Account) } //______________________________________________ -void DigiParams::print() const +template +void DigiParams::print() const { // print settings - printf("TRK digitization params:\n"); + printf("%s digitization params:\n", o2::detectors::DetID::getName(DetIDV)); printf("Threshold (N electrons) : %d\n", mChargeThreshold); printf("Min N electrons to account : %d\n", mMinChargeToAccount); printf("Number of charge sharing steps : %d\n", mNSimSteps); @@ -68,7 +73,8 @@ void DigiParams::print() const mSignalShape.print(); } -void DigiParams::setResponse(const o2::itsmft::AlpideSimResponse* resp) +template +void DigiParams::setResponse(const o2::itsmft::AlpideSimResponse* resp) { LOG(debug) << "Response function data path: " << resp->getDataPath(); LOG(debug) << "Response function info: "; @@ -76,5 +82,8 @@ void DigiParams::setResponse(const o2::itsmft::AlpideSimResponse* resp) if (!resp) { LOGP(fatal, "cannot set response function from null"); } - mResponse = std::make_unique(resp); + mResponse = std::make_unique(resp); } + +template class o2::trkft3::DigiParams; +template class o2::trkft3::DigiParams; diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/Digitizer.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/Digitizer.cxx similarity index 91% rename from Detectors/Upgrades/ALICE3/TRK/simulation/src/Digitizer.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/Digitizer.cxx index 890c272fefbc2..5e8faca2830fe 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/Digitizer.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/Digitizer.cxx @@ -11,11 +11,10 @@ /// \file Digitizer.cxx -#include "DataFormatsITSMFT/Digit.h" +#include "DataFormatsTRKFT3/Digit.h" #include "TRKBase/SegmentationChip.h" -#include "TRKSimulation/DPLDigitizerParam.h" -#include "TRKSimulation/TRKLayer.h" -#include "TRKSimulation/Digitizer.h" +#include "TRKBase/Specs.h" +#include "TRKFT3Simulation/Digitizer.h" #include "DetectorsRaw/HBFUtils.h" #include @@ -26,15 +25,16 @@ #include #include // for LOG -using o2::itsmft::Digit; -using o2::trk::Hit; +using o2::trkft3::Digit; +using o2::trkft3::Hit; using Segmentation = o2::trk::SegmentationChip; -using namespace o2::trk; +using namespace o2::trkft3; using namespace o2::itsmft; // using namespace o2::base; //_______________________________________________________________________ -void Digitizer::init() +template +void Digitizer::init() { LOG(info) << "Initializing digitizer"; mNumberOfChips = mGeometry->getNumberOfChips(); @@ -88,10 +88,7 @@ void Digitizer::init() mSimRespMLOTShift = mChipSimRespMLOT->getDepthMax() - thicknessMLOT / 2.f; // the shift should be done considering the rescaling done to adapt to the wrong silicon thickness. TODO: remove the scaling factor for the depth when the silicon thickness match the simulated response - // importing the parameters from DPLDigitizerParam.h - auto& dOptTRK = DPLDigitizerParam::Instance(); - - LOGP(info, "TRK Digitizer is initialised."); + LOGP(info, "{} Digitizer is initialised.", o2::detectors::DetID::getName(DetID)); mParams.print(); LOGP(info, "VD shift = {} ; ML/OT shift = {} = {} - {}", mSimRespVDShift, mSimRespMLOTShift, mChipSimRespMLOT->getDepthMax(), thicknessMLOT / 2.f); LOGP(info, "VD pixel scale on x = {} ; z = {}", mSimRespVDScaleX, mSimRespVDScaleZ); @@ -101,32 +98,34 @@ void Digitizer::init() mIRFirstSampledTF = o2::raw::HBFUtils::Instance().getFirstSampledTFIR(); } -const o2::trk::ChipSimResponse* Digitizer::getChipResponse(int chipID) +template +const o2::trkft3::ChipSimResponse* Digitizer::getChipResponse(int chipID) { if (mGeometry->getSubDetID(chipID) == 0) { /// VD return mChipSimRespVD; } - else if (mGeometry->getSubDetID(chipID) == 1) { /// ML/OT + else if (mGeometry->getSubDetID(chipID) == 1 || mGeometry->getSubDetID(chipID) == 2) { /// ML/OT return mChipSimRespMLOT; } return nullptr; }; //_______________________________________________________________________ -void Digitizer::process(const std::vector* hits, int evID, int srcID, int layer) +template +void Digitizer::process(const std::vector* hits, int evID, int srcID, int layer) { // digitize single event, the time must have been set beforehand LOG(info) << " Digitizing " << mGeometry->getName() << " (ID: " << mGeometry->getDetID() << ") hits of event " << evID << " from source " << srcID << " at time " << mEventTime.getTimeNS() << " ROFrame = " << mNewROFrame - << " Min/Max ROFrames " << mROFrameMin << "/" << mROFrameMax; + << " Min/Max ROFrames " << mROFrameMin << "/" << mROFrameMax << " layer " << layer; - std::cout << "Printing segmentation info: " << std::endl; - SegmentationChip::Print(); + // std::cout << "Printing segmentation info: " << std::endl; + // SegmentationChip::Print(); - // // is there something to flush ? + // is there something to flush ? if (mNewROFrame > mROFrameMin) { fillOutputContainer(mNewROFrame - 1, layer); // flush out all frames preceding the new one } @@ -144,14 +143,15 @@ void Digitizer::process(const std::vector* hits, int evID, int srcID, int l if (layer < 0) { return true; } - return mGeometry->getLayerTRK((*hits)[idx].GetDetectorID()) == layer; + return getROFLayer((*hits)[idx].GetDetectorID()) == layer; })) { processHit((*hits)[i], mROFrameMax, evID, srcID, layer); } } //_______________________________________________________________________ -void Digitizer::setEventTime(const o2::InteractionTimeRecord& irt, int layer) +template +void Digitizer::setEventTime(const o2::InteractionTimeRecord& irt, int layer) { LOG(info) << "Setting event time to " << irt.getTimeNS() << " ns after orbit 0 bc 0"; // assign event time in ns @@ -164,15 +164,14 @@ void Digitizer::setEventTime(const o2::InteractionTimeRecord& irt, int layer) nbc--; } + mROFsWrtFirstRO = std::floor(float(nbc) / mParams.getROFrameLengthInBC(layer)); if (nbc < 0) { mNewROFrame = 0; - mIsBeforeFirstRO = true; } else { mNewROFrame = nbc / mParams.getROFrameLengthInBC(layer); - mIsBeforeFirstRO = false; } - LOG(debug) << " NewROFrame " << mNewROFrame << " = " << nbc << "/" << mParams.getROFrameLengthInBC(layer) << " (nbc/mParams.getROFrameLengthInBC()"; + LOG(debug) << " NewROFrame " << mNewROFrame << " nbc " << nbc << " ROFsWrtFirstRO " << mROFsWrtFirstRO; // in continuous mode depends on starts of periodic readout frame mCollisionTimeWrtROF += (nbc % mParams.getROFrameLengthInBC(layer)) * o2::constants::lhc::LHCBunchSpacingNS; @@ -188,7 +187,8 @@ void Digitizer::setEventTime(const o2::InteractionTimeRecord& irt, int layer) } //_______________________________________________________________________ -void Digitizer::fillOutputContainer(uint32_t frameLast, int layer) +template +void Digitizer::fillOutputContainer(uint32_t frameLast, int layer) { // // fill output with digits from min.cached up to requested frame, generating the noise beforehand if (frameLast > mROFrameMax) { @@ -199,7 +199,7 @@ void Digitizer::fillOutputContainer(uint32_t frameLast, int layer) LOG(info) << "Filling " << mGeometry->getName() << " digits output for RO frames " << mROFrameMin << ":" << frameLast; - o2::itsmft::ROFRecord rcROF; /// using temporarly itsmft::ROFRecord + o2::trkft3::ROFRecord rcROF; /// using temporarly trkft3::ROFRecord // we have to write chips in RO increasing order, therefore have to loop over the frames here for (; mROFrameMin <= frameLast; mROFrameMin++) { @@ -208,7 +208,7 @@ void Digitizer::fillOutputContainer(uint32_t frameLast, int layer) auto& extra = *(mExtraBuff.front().get()); for (auto& chip : mChips) { - if (chip.isDisabled() || (layer >= 0 && mGeometry->getLayerTRK(chip.getChipIndex()) != layer)) { + if (chip.isDisabled() || (layer >= 0 && getROFLayer(chip.getChipIndex()) != layer)) { continue; } chip.addNoise(mROFrameMin, mROFrameMin, &mParams, mGeometry->getSubDetID(chip.getChipIndex()), mGeometry->getLayer(chip.getChipIndex())); /// TODO: add noise @@ -252,16 +252,17 @@ void Digitizer::fillOutputContainer(uint32_t frameLast, int layer) } //_______________________________________________________________________ -void Digitizer::processHit(const o2::trk::Hit& hit, uint32_t& maxFr, int evID, int srcID, int rofLayer) +template +void Digitizer::processHit(const o2::trkft3::Hit& hit, uint32_t& maxFr, int evID, int srcID, int rofLayer) { int chipID = hit.GetDetectorID(); //// the chip ID at the moment is not referred to the chip but to a wider detector element (e.g. quarter of layer or disk in VD, stave in ML, half stave in OT) int subDetID = mGeometry->getSubDetID(chipID); - int layer = mGeometry->getLayer(chipID); - int disk = mGeometry->getDisk(chipID); + int layer = mGeometry->getLayer(chipID); // local layer nr for response + int disk = getDisk(chipID); if (disk != -1) { - LOG(debug) << "Skipping disk " << disk; + LOG(debug) << "Skipping VD disk " << disk; return; // skipping hits on disks for the moment } @@ -283,7 +284,7 @@ void Digitizer::processHit(const o2::trk::Hit& hit, uint32_t& maxFr, int evID, i return; } timeInROF += mCollisionTimeWrtROF; - if (mIsBeforeFirstRO && timeInROF < 0) { + if (mROFsWrtFirstRO < -1 || (mROFsWrtFirstRO == -1 && timeInROF < 0)) { // disregard this hit because it comes from an event byefore readout starts and it does not effect this RO LOG(debug) << "Ignoring hit with timeInROF = " << timeInROF; return; @@ -402,7 +403,7 @@ void Digitizer::processHit(const o2::trk::Hit& hit, uint32_t& maxFr, int evID, i int rowPrev = -1, colPrev = -1, row, col; float cRowPix = 0.f, cColPix = 0.f; // local coordinate of the current pixel center - const o2::trk::ChipSimResponse* resp = getChipResponse(chipID); + const o2::trkft3::ChipSimResponse* resp = getChipResponse(chipID); // std::cout << "Printing chip response:" << std::endl; // resp->print(); @@ -463,7 +464,7 @@ void Digitizer::processHit(const o2::trk::Hit& hit, uint32_t& maxFr, int evID, i } } } - + LOG(info) << "Response done; adding labels; making digits"; // fire the pixels assuming Poisson(n_response_electrons) o2::MCCompLabel lbl(hit.GetTrackID(), evID, srcID, false); auto roFrameAbs = mNewROFrame + roFrameRel; @@ -498,8 +499,9 @@ void Digitizer::processHit(const o2::trk::Hit& hit, uint32_t& maxFr, int evID, i } //________________________________________________________________________________ -void Digitizer::registerDigits(o2::trk::ChipDigitsContainer& chip, uint32_t roFrame, float tInROF, int nROF, - uint16_t row, uint16_t col, int nEle, o2::MCCompLabel& lbl, int layer) +template +void Digitizer::registerDigits(o2::trkft3::ChipDigitsContainer& chip, uint32_t roFrame, float tInROF, int nROF, + uint16_t row, uint16_t col, int nEle, o2::MCCompLabel& lbl, int layer) { // Register digits for given pixel, accounting for the possible signal contribution to // multiple ROFrame. The signal starts at time tInROF wrt the start of provided roFrame @@ -552,3 +554,6 @@ void Digitizer::registerDigits(o2::trk::ChipDigitsContainer& chip, uint32_t roFr } } } + +template class o2::trkft3::Digitizer; +template class o2::trkft3::Digitizer; diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/TRKFT3SimulationLinkDef.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/TRKFT3SimulationLinkDef.h new file mode 100644 index 0000000000000..e11378f471ec3 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/simulation/src/TRKFT3SimulationLinkDef.h @@ -0,0 +1,27 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifdef __CLING__ + +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ class o2::trkft3::ChipDigitsContainer + ; +#pragma link C++ class o2::trkft3::ChipSimResponse + ; +#pragma link C++ class o2::trkft3::Digitizer < o2::detectors::DetID::TRK> + ; +#pragma link C++ class o2::trkft3::Digitizer < o2::detectors::DetID::FT3> + ; +#pragma link C++ class o2::trkft3::DPLDigitizerParam < o2::detectors::DetID::TRK> + ; +#pragma link C++ class o2::conf::ConfigurableParamHelper < o2::trkft3::DPLDigitizerParam < o2::detectors::DetID::TRK>> + ; +#pragma link C++ class o2::trkft3::DPLDigitizerParam < o2::detectors::DetID::FT3> + ; +#pragma link C++ class o2::conf::ConfigurableParamHelper < o2::trkft3::DPLDigitizerParam < o2::detectors::DetID::FT3>> + ; + +#endif diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/CMakeLists.txt similarity index 96% rename from Detectors/Upgrades/ALICE3/TRK/workflow/CMakeLists.txt rename to Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/CMakeLists.txt index e3309d78f47ea..f437b53715149 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/CMakeLists.txt @@ -20,7 +20,7 @@ o2_add_library(TRKWorkflow O2::GPUWorkflow O2::SimConfig O2::DataFormatsITSMFT - O2::DataFormatsTRK + O2::DataFormatsTRKFT3 O2::SimulationDataFormat O2::DPLUtils O2::TRKBase diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/README.md b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/README.md similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/workflow/README.md rename to Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/README.md diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/ClusterWriterSpec.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/ClusterWriterSpec.h similarity index 85% rename from Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/ClusterWriterSpec.h rename to Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/ClusterWriterSpec.h index 50d823b497bb9..3fcc4253fed7f 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/ClusterWriterSpec.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/ClusterWriterSpec.h @@ -17,6 +17,8 @@ namespace o2::trk { +framework::DataProcessorSpec getTRKClusterWriterSpec(bool useMC); +framework::DataProcessorSpec getFT3ClusterWriterSpec(bool useMC); framework::DataProcessorSpec getClusterWriterSpec(bool useMC); } // namespace o2::trk diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/ClustererSpec.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/ClustererSpec.h similarity index 97% rename from Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/ClustererSpec.h rename to Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/ClustererSpec.h index 9d072e85d574a..0166aa14462a8 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/ClustererSpec.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/ClustererSpec.h @@ -34,7 +34,7 @@ class ClustererDPL : public o2::framework::Task static constexpr int mLayers = o2::trk::AlmiraParam::kNLayers; bool mUseMC = true; int mNThreads = 1; - o2::trk::Clusterer mClusterer; + o2::trk::TRKClusterer mClusterer; #ifdef O2_WITH_ACTS bool mUseACTS = false; o2::trk::ClustererACTS mClustererACTS; diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/DigitReaderSpec.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/DigitReaderSpec.h similarity index 91% rename from Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/DigitReaderSpec.h rename to Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/DigitReaderSpec.h index 92b64e0815cfb..de04358b227eb 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/DigitReaderSpec.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/DigitReaderSpec.h @@ -16,14 +16,13 @@ #include "TFile.h" #include "TTree.h" -#include "DataFormatsITSMFT/Digit.h" +#include "DataFormatsTRKFT3/Digit.h" +#include "DataFormatsTRKFT3/ROFRecord.h" #include "DataFormatsITSMFT/GBTCalibData.h" -#include "DataFormatsITSMFT/ROFRecord.h" #include "SimulationDataFormat/IOMCTruthContainerView.h" #include "Framework/DataProcessorSpec.h" #include "Framework/Task.h" #include "Headers/DataHeader.h" -#include "DataFormatsITSMFT/ROFRecord.h" #include "DetectorsCommonDataFormats/DetID.h" #include "TRKBase/AlmiraParam.h" @@ -51,9 +50,9 @@ class DigitReader : public Task static constexpr int mLayers = o2::trk::AlmiraParam::kNLayers; - std::vector*> mDigits{nullptr}; + std::vector*> mDigits{nullptr}; std::vector mCalib, *mCalibPtr = &mCalib; - std::vector*> mDigROFRec{nullptr}; + std::vector*> mDigROFRec{nullptr}; std::vector mPLabels{nullptr}; o2::header::DataOrigin mOrigin = o2::header::gDataOriginInvalid; diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/DigitWriterSpec.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/DigitWriterSpec.h similarity index 88% rename from Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/DigitWriterSpec.h rename to Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/DigitWriterSpec.h index 9c37d4318bb0f..e5184b132811e 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/DigitWriterSpec.h +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/DigitWriterSpec.h @@ -20,6 +20,7 @@ namespace trk { o2::framework::DataProcessorSpec getTRKDigitWriterSpec(bool mctruth = true, bool dec = false, bool calib = false); +o2::framework::DataProcessorSpec getFT3DigitWriterSpec(bool mctruth = true, bool dec = false, bool calib = false); } // namespace trk } // end namespace o2 diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/RecoWorkflow.h b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/RecoWorkflow.h similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/RecoWorkflow.h rename to Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/include/TRKWorkflow/RecoWorkflow.h diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/src/ClusterWriterSpec.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/ClusterWriterSpec.cxx similarity index 67% rename from Detectors/Upgrades/ALICE3/TRK/workflow/src/ClusterWriterSpec.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/ClusterWriterSpec.cxx index 863915bac0572..ae4407a5136fa 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/src/ClusterWriterSpec.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/ClusterWriterSpec.cxx @@ -21,9 +21,12 @@ #include "Framework/ConcreteDataMatcher.h" #include "Framework/DataRef.h" #include "TRKBase/AlmiraParam.h" +#include "TRKBase/Specs.h" #include "DPLUtils/MakeRootTreeWriterSpec.h" -#include "DataFormatsTRK/Cluster.h" -#include "DataFormatsTRK/ROFRecord.h" +#include "DataFormatsTRKFT3/Cluster.h" +#include "DataFormatsTRKFT3/ROFRecord.h" +#include "DetectorsCommonDataFormats/DetID.h" +#include "Headers/DataHeader.h" #include "SimulationDataFormat/MCCompLabel.h" #include "SimulationDataFormat/MCTruthContainer.h" @@ -34,16 +37,17 @@ namespace o2::trk template using BranchDefinition = MakeRootTreeWriterSpec::BranchDefinition; -using ClustersType = std::vector; using PatternsType = std::vector; -using ROFrameType = std::vector; +using ROFrameType = std::vector; using LabelsType = o2::dataformats::MCTruthContainer; -using ROFRecLblType = std::vector; -DataProcessorSpec getClusterWriterSpec(bool useMC) +template +DataProcessorSpec getClusterWriterSpecT(bool useMC) { - static constexpr o2::header::DataOrigin Origin{o2::header::gDataOriginTRK}; - static constexpr int nLayers = o2::trk::AlmiraParam::kNLayers; + static_assert(DetID == o2::detectors::DetID::TRK || DetID == o2::detectors::DetID::FT3, "only TRK and FT3 cluster writers are supported"); + using ClustersType = std::vector>; + static constexpr o2::header::DataOrigin Origin = DetID == o2::detectors::DetID::TRK ? o2::header::gDataOriginTRK : o2::header::gDataOriginFT3; + const int nLayers = DetID == o2::detectors::DetID::TRK ? o2::trk::AlmiraParam::kNLayers : o2::trk::constants::MLOTDisks::nLayers; const auto detName = Origin.as(); auto compClusterSizes = std::make_shared>(nLayers, 0); @@ -73,37 +77,52 @@ DataProcessorSpec getClusterWriterSpec(bool useMC) vecInpSpecROF.reserve(nLayers); vecInpSpecLbl.reserve(nLayers); for (int iLayer = 0; iLayer < nLayers; iLayer++) { - vecInpSpecClus.emplace_back(getName("compclus", iLayer), Origin, "COMPCLUSTERS", iLayer); - vecInpSpecPatt.emplace_back(getName("patterns", iLayer), Origin, "PATTERNS", iLayer); - vecInpSpecROF.emplace_back(getName("ROframes", iLayer), Origin, "CLUSTERSROF", iLayer); - vecInpSpecLbl.emplace_back(getName("labels", iLayer), Origin, "CLUSTERSMCTR", iLayer); + vecInpSpecClus.emplace_back(getName(detName + "compclus", iLayer), Origin, "COMPCLUSTERS", iLayer); + vecInpSpecPatt.emplace_back(getName(detName + "patterns", iLayer), Origin, "PATTERNS", iLayer); + vecInpSpecROF.emplace_back(getName(detName + "ROframes", iLayer), Origin, "CLUSTERSROF", iLayer); + vecInpSpecLbl.emplace_back(getName(detName + "labels", iLayer), Origin, "CLUSTERSMCTR", iLayer); } return MakeRootTreeWriterSpec(std::format("{}-cluster-writer", detNameLC).c_str(), - "o2clus_trk.root", - MakeRootTreeWriterSpec::TreeAttributes{.name = "o2sim", .title = "Tree with TRK clusters"}, + std::format("o2clus_{}.root", detNameLC).c_str(), + MakeRootTreeWriterSpec::TreeAttributes{.name = "o2sim", .title = "Tree with " + detName + " clusters"}, BranchDefinition{vecInpSpecClus, - "TRKClusterComp", "compact-cluster-branch", + detName + "ClusterComp", "compact-cluster-branch", nLayers, compClustersSizeGetter, getIndex, getName}, BranchDefinition{vecInpSpecPatt, - "TRKClusterPatt", "cluster-pattern-branch", + detName + "ClusterPatt", "cluster-pattern-branch", nLayers, getIndex, getName}, BranchDefinition{vecInpSpecROF, - "TRKClustersROF", "cluster-rof-branch", + detName + "ClustersROF", "cluster-rof-branch", nLayers, logger, getIndex, getName}, BranchDefinition{vecInpSpecLbl, - "TRKClusterMCTruth", "cluster-label-branch", + detName + "ClusterMCTruth", "cluster-label-branch", (useMC ? nLayers : 0), getIndex, getName})(); } +DataProcessorSpec getTRKClusterWriterSpec(bool useMC) +{ + return getClusterWriterSpecT(useMC); +} + +DataProcessorSpec getFT3ClusterWriterSpec(bool useMC) +{ + return getClusterWriterSpecT(useMC); +} + +DataProcessorSpec getClusterWriterSpec(bool useMC) +{ + return getTRKClusterWriterSpec(useMC); +} + } // namespace o2::trk diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/src/ClustererSpec.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/ClustererSpec.cxx similarity index 94% rename from Detectors/Upgrades/ALICE3/TRK/workflow/src/ClustererSpec.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/ClustererSpec.cxx index f91262e021a55..f299c581956a0 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/src/ClustererSpec.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/ClustererSpec.cxx @@ -11,8 +11,8 @@ #include "TRKWorkflow/ClustererSpec.h" #include "DetectorsBase/GeometryManager.h" -#include "DataFormatsTRK/Cluster.h" -#include "DataFormatsTRK/ROFRecord.h" +#include "DataFormatsTRKFT3/Cluster.h" +#include "DataFormatsTRKFT3/ROFRecord.h" #include "Framework/ConfigParamRegistry.h" #include "Framework/Logger.h" #include "SimulationDataFormat/ConstMCTruthContainer.h" @@ -36,8 +36,8 @@ void ClustererDPL::run(o2::framework::ProcessingContext& pc) uint64_t totalClusters = 0; for (int iLayer = 0; iLayer < mLayers; ++iLayer) { - auto digits = pc.inputs().get>(std::format("digits_{}", iLayer)); - auto rofs = pc.inputs().get>(std::format("ROframes_{}", iLayer)); + auto digits = pc.inputs().get>(std::format("digits_{}", iLayer)); + auto rofs = pc.inputs().get>(std::format("ROframes_{}", iLayer)); gsl::span labelbuffer; if (mUseMC) { @@ -45,9 +45,9 @@ void ClustererDPL::run(o2::framework::ProcessingContext& pc) } o2::dataformats::ConstMCTruthContainerView labels(labelbuffer); - std::vector clusters; + std::vector clusters; std::vector patterns; - std::vector clusterROFs; + std::vector clusterROFs; std::unique_ptr> clusterLabels; if (mUseMC) { clusterLabels = std::make_unique>(); diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/src/DigitReaderSpec.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/DigitReaderSpec.cxx similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/workflow/src/DigitReaderSpec.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/DigitReaderSpec.cxx diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/src/DigitWriterSpec.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/DigitWriterSpec.cxx similarity index 80% rename from Detectors/Upgrades/ALICE3/TRK/workflow/src/DigitWriterSpec.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/DigitWriterSpec.cxx index 591b084aee3ba..e1d5d3cbcf5f2 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/src/DigitWriterSpec.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/DigitWriterSpec.cxx @@ -15,18 +15,20 @@ #include "Framework/ConcreteDataMatcher.h" #include "Framework/DataRef.h" #include "TRKBase/AlmiraParam.h" +#include "TRKBase/Specs.h" #include "DPLUtils/MakeRootTreeWriterSpec.h" -#include "DataFormatsITSMFT/Digit.h" +#include "DataFormatsTRKFT3/Digit.h" #include "DataFormatsITSMFT/GBTCalibData.h" #include "Headers/DataHeader.h" #include "DetectorsCommonDataFormats/DetID.h" -#include "DataFormatsITSMFT/ROFRecord.h" +#include "DataFormatsTRKFT3/ROFRecord.h" #include "SimulationDataFormat/ConstMCTruthContainer.h" #include "SimulationDataFormat/IOMCTruthContainerView.h" #include "SimulationDataFormat/MCCompLabel.h" #include #include #include +#include #include using namespace o2::framework; @@ -41,20 +43,26 @@ template using BranchDefinition = MakeRootTreeWriterSpec::BranchDefinition; using MCCont = o2::dataformats::ConstMCTruthContainer; -DataProcessorSpec getTRKDigitWriterSpec(bool mctruth, bool dec, bool calib) +template +DataProcessorSpec getDigitWriterSpec(bool mctruth, bool dec, bool calib) { - static constexpr o2::header::DataOrigin Origin = o2::header::gDataOriginTRK; - const int mLayers = o2::trk::AlmiraParam::kNLayers; - std::string detStr = "TRK"; - std::string detStrL = dec ? "o2_trk" : "trk"; + static_assert(DetID == o2::detectors::DetID::TRK || DetID == o2::detectors::DetID::FT3, "only TRK and FT3 digit writers are supported"); + static constexpr o2::header::DataOrigin Origin = DetID == o2::detectors::DetID::TRK ? o2::header::gDataOriginTRK : o2::header::gDataOriginFT3; + const int mLayers = DetID == o2::detectors::DetID::TRK ? o2::trk::AlmiraParam::kNLayers : o2::trk::constants::MLOTDisks::nLayers; + std::string detStr = o2::detectors::DetID(DetID).getName(); + auto detStrL = detStr; + std::transform(detStrL.begin(), detStrL.end(), detStrL.begin(), [](unsigned char c) { return std::tolower(c); }); + if (dec) { + detStrL = "o2_" + detStrL; + } auto digitSizes = std::make_shared>(mLayers, 0); - auto digitSizeGetter = [digitSizes](std::vector const& inDigits, DataRef const& ref) { + auto digitSizeGetter = [digitSizes](std::vector const& inDigits, DataRef const& ref) { auto const* dh = DataRefUtils::getHeader(ref); (*digitSizes)[dh->subSpecification] = inDigits.size(); }; auto rofSizes = std::make_shared>(mLayers, 0); - auto rofSizeGetter = [rofSizes](std::vector const& inROFs, DataRef const& ref) { + auto rofSizeGetter = [rofSizes](std::vector const& inROFs, DataRef const& ref) { auto const* dh = DataRefUtils::getHeader(ref); (*rofSizes)[dh->subSpecification] = inROFs.size(); }; @@ -110,17 +118,17 @@ DataProcessorSpec getTRKDigitWriterSpec(bool mctruth, bool dec, bool calib) vecInpSpecLbl.emplace_back(getName(detStr + "_digitsMCTR", iLayer), Origin, "DIGITSMCTR", iLayer); } - return MakeRootTreeWriterSpec(("TRKDigitWriter" + std::string(dec ? "_dec" : "")).c_str(), + return MakeRootTreeWriterSpec((detStr + "DigitWriter" + std::string(dec ? "_dec" : "")).c_str(), (detStrL + "digits.root").c_str(), MakeRootTreeWriterSpec::TreeAttributes{.name = "o2sim", .title = detStr + " Digits tree"}, MakeRootTreeWriterSpec::CustomClose(finishWriting), - BranchDefinition>{vecInpSpecDig, + BranchDefinition>{vecInpSpecDig, detStr + "Digit", "digit-branch", mLayers, digitSizeGetter, getIndex, getName}, - BranchDefinition>{vecInpSpecROF, + BranchDefinition>{vecInpSpecROF, detStr + "DigitROF", "digit-rof-branch", mLayers, rofSizeGetter, @@ -137,5 +145,15 @@ DataProcessorSpec getTRKDigitWriterSpec(bool mctruth, bool dec, bool calib) (calib ? 1 : 0)})(); } +DataProcessorSpec getTRKDigitWriterSpec(bool mctruth, bool dec, bool calib) +{ + return getDigitWriterSpec(mctruth, dec, calib); +} + +DataProcessorSpec getFT3DigitWriterSpec(bool mctruth, bool dec, bool calib) +{ + return getDigitWriterSpec(mctruth, dec, calib); +} + } // end namespace trk } // end namespace o2 diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/src/RecoWorkflow.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/RecoWorkflow.cxx similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/workflow/src/RecoWorkflow.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/RecoWorkflow.cxx diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/src/trk-reco-workflow.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/trk-reco-workflow.cxx similarity index 100% rename from Detectors/Upgrades/ALICE3/TRK/workflow/src/trk-reco-workflow.cxx rename to Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/trk-reco-workflow.cxx diff --git a/Detectors/Upgrades/ITS3/simulation/include/ITS3Simulation/Digitizer.h b/Detectors/Upgrades/ITS3/simulation/include/ITS3Simulation/Digitizer.h index 78bb9923dae97..90dda6fd67393 100644 --- a/Detectors/Upgrades/ITS3/simulation/include/ITS3Simulation/Digitizer.h +++ b/Detectors/Upgrades/ITS3/simulation/include/ITS3Simulation/Digitizer.h @@ -111,7 +111,7 @@ class Digitizer : public TObject uint32_t mROFrameMin = 0; ///< lowest RO frame of current digits uint32_t mROFrameMax = 0; ///< highest RO frame of current digits uint32_t mNewROFrame = 0; ///< ROFrame corresponding to provided time - bool mIsBeforeFirstRO = false; + int mROFsWrtFirstRO = 0; uint32_t mEventROFrameMin = 0xffffffff; ///< lowest RO frame for processed events (w/o automatic noise ROFs) uint32_t mEventROFrameMax = 0; ///< highest RO frame forfor processed events (w/o automatic noise ROFs) diff --git a/Detectors/Upgrades/ITS3/simulation/src/Digitizer.cxx b/Detectors/Upgrades/ITS3/simulation/src/Digitizer.cxx index 6fbed92bb1400..9d048ea1f31ca 100644 --- a/Detectors/Upgrades/ITS3/simulation/src/Digitizer.cxx +++ b/Detectors/Upgrades/ITS3/simulation/src/Digitizer.cxx @@ -128,15 +128,13 @@ void Digitizer::setEventTime(const o2::InteractionTimeRecord& irt, int layer) } // we might get interactions to digitize from before // the first sampled IR + mROFsWrtFirstRO = std::floor(float(nbc) / mParams.getROFrameLengthInBC(layer)); if (nbc < 0) { mNewROFrame = 0; - // this event is before the first RO - mIsBeforeFirstRO = true; } else { mNewROFrame = nbc / mParams.getROFrameLengthInBC(layer); - mIsBeforeFirstRO = false; } - LOG(debug) << " NewROFrame " << mNewROFrame << " nbc " << nbc; + LOG(debug) << " NewROFrame " << mNewROFrame << " nbc " << nbc << " ROFsWrtFirstRO " << mROFsWrtFirstRO; // in continuous mode depends on starts of periodic readout frame mCollisionTimeWrtROF += (nbc % mParams.getROFrameLengthInBC(layer)) * o2::constants::lhc::LHCBunchSpacingNS; @@ -240,7 +238,7 @@ void Digitizer::processHit(const o2::itsmft::Hit& hit, uint32_t& maxFr, int evID if (isContinuous()) { timeInROF += mCollisionTimeWrtROF; } - if (mIsBeforeFirstRO && timeInROF < 0) { + if (mROFsWrtFirstRO < -1 || (mROFsWrtFirstRO == -1 && timeInROF < 0)) { // disregard this hit because it comes from an event before readout starts and it does not effect this RO return; } diff --git a/Detectors/Upgrades/README.md b/Detectors/Upgrades/README.md index febcb18e746be..c32b1e4a44037 100644 --- a/Detectors/Upgrades/README.md +++ b/Detectors/Upgrades/README.md @@ -18,7 +18,6 @@ Currently two sections are included: diff --git a/Detectors/ZDC/raw/CMakeLists.txt b/Detectors/ZDC/raw/CMakeLists.txt index 5f4983d6fc31b..da11ea3d9810b 100644 --- a/Detectors/ZDC/raw/CMakeLists.txt +++ b/Detectors/ZDC/raw/CMakeLists.txt @@ -25,9 +25,6 @@ o2_add_library(ZDCRaw O2::ZDCBase O2::ZDCSimulation) -o2_target_root_dictionary(ZDCRaw - HEADERS include/ZDCRaw/DumpRaw.h) - o2_add_executable(raw-parser COMPONENT_NAME zdc SOURCES src/raw-parser.cxx diff --git a/Framework/AnalysisSupport/src/TTreePlugin.cxx b/Framework/AnalysisSupport/src/TTreePlugin.cxx index 1a6f48ebef5b4..fa291ce8cbebc 100644 --- a/Framework/AnalysisSupport/src/TTreePlugin.cxx +++ b/Framework/AnalysisSupport/src/TTreePlugin.cxx @@ -211,6 +211,9 @@ auto readBoolValues = [](uint8_t* target, ReadOps& op, TBufferFile& rootBuffer) while (readEntries < op.rootBranchEntries) { auto beginValue = readEntries; readLast = op.branch->GetBulkRead().GetBulkEntries(readEntries, rootBuffer); + if (readLast < 0) { + throw runtime_error_f("Error while reading branch %s starting from %d.", op.branch->GetName(), readEntries); + } int size = readLast * op.listSize; readEntries += readLast; for (int i = beginValue; i < beginValue + size; ++i) { @@ -228,7 +231,18 @@ auto readVLAValues = [](uint8_t* target, ReadOps& op, ReadOps const& offsetOp, T rootBuffer.Reset(); while (readEntries < op.rootBranchEntries) { auto readLast = op.branch->GetBulkRead().GetEntriesSerialized(readEntries, rootBuffer); + if (readLast < 0) { + throw runtime_error_f("Error while reading branch %s starting from %d.", op.branch->GetName(), readEntries); + } + if (readEntries + readLast > op.rootBranchEntries) { + throw runtime_error_f("Invalid read range for branch %s: starting from %d, read %d entries, total entries %lld.", + op.branch->GetName(), readEntries, readLast, static_cast(op.rootBranchEntries)); + } int size = offsets[readEntries + readLast] - offsets[readEntries]; + if (size < 0) { + throw runtime_error_f("Invalid offset range for branch %s: offsets[%d]=%d, offsets[%d]=%d.", + op.branch->GetName(), readEntries, offsets[readEntries], readEntries + readLast, offsets[readEntries + readLast]); + } readEntries += readLast; bigEndianCopy(target, rootBuffer.GetCurrent(), size, op.typeSize); target += (ptrdiff_t)(size * op.typeSize); diff --git a/Framework/CCDBSupport/src/AnalysisCCDBHelpers.cxx b/Framework/CCDBSupport/src/AnalysisCCDBHelpers.cxx index 21fdae4a57760..0b554a9dfdd7f 100644 --- a/Framework/CCDBSupport/src/AnalysisCCDBHelpers.cxx +++ b/Framework/CCDBSupport/src/AnalysisCCDBHelpers.cxx @@ -11,6 +11,7 @@ #include "AnalysisCCDBHelpers.h" #include "CCDBFetcherHelper.h" +#include "Framework/ArrowTypes.h" #include "Framework/DataProcessingStats.h" #include "Framework/DeviceSpec.h" #include "Framework/TimingInfo.h" @@ -22,6 +23,7 @@ #include "Framework/DanglingEdgesContext.h" #include "Framework/ConfigContext.h" #include "Framework/ConfigParamsHelper.h" +#include #include #include #include @@ -109,20 +111,49 @@ AlgorithmSpec AnalysisCCDBHelpers::fetchFromCCDB(ConfigContext const& /*ctx*/) auto it = ccdbUrls.find(m.name); fieldMetadata->Append("url", it != ccdbUrls.end() ? it->second : m.defaultValue.asString()); auto columnName = m.name.substr(strlen("ccdb:")); +#if (FAIRMQ_VERSION_DEC >= 111000) + fields.emplace_back(std::make_shared(columnName, soa::asArrowDataType(), false, fieldMetadata)); +#else fields.emplace_back(std::make_shared(columnName, arrow::binary_view(), false, fieldMetadata)); +#endif } schemas.emplace_back(std::make_shared(fields, schemaMetadata)); } +#if (FAIRMQ_VERSION_DEC >= 111000) + std::vector>> allbuilders; +#else + std::vector>> allbuilders; +#endif + allbuilders.resize([&schemas]() { size_t size = 0; for (auto& schema : schemas) { size += schema->num_fields(); }; return size; }()); + auto* pool = arrow::default_memory_pool(); + + int idx = 0; + int sidx = 0; + for (auto const& schema : schemas) { + for (auto const& _ : schema->fields()) { +#if (FAIRMQ_VERSION_DEC >= 111000) + auto value_builder = std::make_shared(); + allbuilders[idx] = std::make_pair(sidx, std::make_shared(pool, std::move(value_builder), 3)); +#else + allbuilders[idx] = std::make_pair(sidx, std::make_shared()); +#endif + ++idx; + } + ++sidx; + } + std::shared_ptr helper = std::make_shared(); CCDBFetcherHelper::initialiseHelper(*helper, options); std::unordered_map bindings; fillValidRoutes(*helper, spec.outputs, bindings); - return adaptStateless([schemas, bindings, helper](InputRecord& inputs, DataTakingContext& dtc, DataAllocator& allocator, TimingInfo& timingInfo, DataProcessingStats& stats) { + return adaptStateless([schemas, bindings, helper, allbuilders](InputRecord& inputs, DataTakingContext& dtc, DataAllocator& allocator, TimingInfo& timingInfo, DataProcessingStats& stats) { O2_SIGNPOST_ID_GENERATE(sid, ccdb); O2_SIGNPOST_START(ccdb, sid, "fetchFromAnalysisCCDB", "Fetching CCDB objects for analysis%" PRIu64, (uint64_t)timingInfo.timeslice); - for (auto& schema : schemas) { + std::ranges::for_each(allbuilders, [](auto& builder) { builder.second->Reset(); }); + for (auto i = 0U; i < schemas.size(); ++i) { + auto& schema = schemas[i]; std::vector ops; auto inputBinding = *schema->metadata()->Get("sourceTable"); auto inputMatcher = DataSpecUtils::fromString(*schema->metadata()->Get("sourceMatcher")); @@ -134,20 +165,32 @@ AlgorithmSpec AnalysisCCDBHelpers::fetchFromCCDB(ConfigContext const& /*ctx*/) auto table = inputs.get(inputMatcher)->asArrowTable(); // FIXME: make the fTimestamp column configurable. auto timestampColumn = table->GetColumnByName("fTimestamp"); + auto reserveSize = timestampColumn->length(); O2_SIGNPOST_EVENT_EMIT_INFO(ccdb, sid, "fetchFromAnalysisCCDB", "There are %zu bindings available", bindings.size()); - for (auto& binding : bindings) { + for (auto const& binding : bindings) { O2_SIGNPOST_EVENT_EMIT_INFO(ccdb, sid, "fetchFromAnalysisCCDB", "* %{public}s: %d", binding.first.c_str(), binding.second); } int outputRouteIndex = bindings.at(outRouteDesc); auto& spec = helper->routes[outputRouteIndex].matcher; - std::vector> builders; - for (auto const& _ : schema->fields()) { - builders.emplace_back(std::make_shared()); + auto concrete = DataSpecUtils::asConcreteDataMatcher(spec); + Output output{concrete.origin, concrete.description, concrete.subSpec}; + auto builders = allbuilders | std::views::filter([&i](auto const& builder) { return builder.first == i; }); + unsigned int numBuilders = std::ranges::count_if(allbuilders, [&i](auto const& builder) { return builder.first == i; }); + arrow::Status status; + std::ranges::for_each(builders, [&status, &reserveSize](auto& builder) { + if (reserveSize > builder.second->capacity()) { + status &= builder.second->Reserve(reserveSize - builder.second->capacity()); + } + }); + if (!status.ok()) { + throw framework::runtime_error_f("Failed to reserve arrays: ", status.ToString().c_str()); } + std::vector lastIds(numBuilders, DataAllocator::CacheId{.value = -1, .handle = -1, .segment = -1}); + for (auto ci = 0; ci < timestampColumn->num_chunks(); ++ci) { std::shared_ptr chunk = timestampColumn->chunk(ci); auto const* timestamps = chunk->data()->GetValuesSafe(1); @@ -171,15 +214,30 @@ AlgorithmSpec AnalysisCCDBHelpers::fetchFromCCDB(ConfigContext const& /*ctx*/) O2_SIGNPOST_START(ccdb, sid, "handlingResponses", "Got %zu responses from server.", responses.size()); - if (builders.size() != responses.size()) { - LOGP(fatal, "Not enough responses (expected {}, found {})", builders.size(), responses.size()); + if (numBuilders != responses.size()) { + LOGP(fatal, "Not enough responses (expected {}, found {})", numBuilders, responses.size()); } arrow::Status result; - for (size_t bi = 0; bi < responses.size(); bi++) { - auto& builder = builders[bi]; + + int bi = 0; + for (auto& builder : builders) { auto& response = responses[bi]; + auto& lastId = lastIds[bi]; + if (response.id.value != lastId.value) { + lastId.value = response.id.value; + allocator.adoptFromCache(output, response.id, header::gSerializationMethodCCDB); + } +#if (FAIRMQ_VERSION_DEC >= 111000) + result &= builder.second->Append(); + auto* value_builder = dynamic_cast(builder.second->value_builder()); + result &= value_builder->Append(response.id.handle); + result &= value_builder->Append(response.id.segment); + result &= value_builder->Append(response.size); +#else char const* address = reinterpret_cast(response.id.value); - result &= builder->Append(std::string_view(address, response.size)); + result &= builder.second->Append(std::string_view(address, response.size)); +#endif + ++bi; } if (!result.ok()) { LOGP(fatal, "Error adding results from CCDB"); @@ -188,12 +246,9 @@ AlgorithmSpec AnalysisCCDBHelpers::fetchFromCCDB(ConfigContext const& /*ctx*/) } } arrow::ArrayVector arrays; - for (auto& builder : builders) { - arrays.push_back(*builder->Finish()); - } + std::ranges::for_each(builders, [&arrays](auto& builder) { arrays.push_back(*builder.second->Finish()); }); auto outTable = arrow::Table::Make(schema, arrays); - auto concrete = DataSpecUtils::asConcreteDataMatcher(spec); - allocator.adopt(Output{concrete.origin, concrete.description, concrete.subSpec}, outTable); + allocator.adopt(output, outTable); } stats.updateStats({(int)ProcessingStatsId::CCDB_CACHE_FETCHED_BYTES, DataProcessingStats::Op::Set, (int64_t)helper->totalFetchedBytes}); diff --git a/Framework/CCDBSupport/src/CCDBFetcherHelper.cxx b/Framework/CCDBSupport/src/CCDBFetcherHelper.cxx index 6fa5b88222ea3..e4a9556c59b17 100644 --- a/Framework/CCDBSupport/src/CCDBFetcherHelper.cxx +++ b/Framework/CCDBSupport/src/CCDBFetcherHelper.cxx @@ -167,7 +167,6 @@ auto CCDBFetcherHelper::populateCacheWith(std::shared_ptr con DataTakingContext& dtc, DataAllocator& allocator) -> std::vector { - int objCnt = -1; // We use the timeslice, so that we hook into the same interval as the rest of the // callback. static bool isOnline = isOnlineRun(dtc); @@ -175,10 +174,9 @@ auto CCDBFetcherHelper::populateCacheWith(std::shared_ptr con auto sid = _o2_signpost_id_t{(int64_t)timingInfo.timeslice}; O2_SIGNPOST_START(ccdb, sid, "populateCacheWith", "Starting to populate cache with CCDB objects"); std::vector responses; - for (auto& op : ops) { + for (auto const& op : ops) { int64_t timestampToUse = op.timestamp; O2_SIGNPOST_EVENT_EMIT(ccdb, sid, "populateCacheWith", "Fetching object for route %{public}s", DataSpecUtils::describe(op.spec).data()); - objCnt++; auto concrete = DataSpecUtils::asConcreteDataMatcher(op.spec); Output output{concrete.origin, concrete.description, concrete.subSpec}; auto&& v = allocator.makeVector(output); @@ -198,7 +196,7 @@ auto CCDBFetcherHelper::populateCacheWith(std::shared_ptr con concrete.origin.as(), concrete.description.as(), int(concrete.subSpec)); } } - for (auto m : op.metadata) { + for (auto const& m : op.metadata) { O2_SIGNPOST_EVENT_EMIT(ccdb, sid, "populateCacheWith", "Adding metadata %{public}s: %{public}s to the request", m.key.data(), m.value.data()); metadata[m.key] = m.value; } @@ -215,7 +213,7 @@ auto CCDBFetcherHelper::populateCacheWith(std::shared_ptr con uint64_t cachePopulatedAt = url2uuid->second.cachePopulatedAt; // If timestamp is before the time the element was cached or after the claimed validity, we need to check validity, again // when online. - bool cacheExpired = (validUntil <= timestampToUse) || (op.timestamp < cachePopulatedAt); + bool cacheExpired = (validUntil <= (uint64_t)timestampToUse) || ((uint64_t)op.timestamp < cachePopulatedAt); if (isOnline || cacheExpired) { if (!helper->useTFSlice) { checkValidity = chRate > 0 ? (std::abs(int(timingInfo.tfCounter - url2uuid->second.lastCheckedTF)) >= chRate) : (timingInfo.tfCounter % -chRate) == 0; @@ -291,7 +289,7 @@ auto CCDBFetcherHelper::populateCacheWith(std::shared_ptr con O2_SIGNPOST_EVENT_EMIT(ccdb, sid, "populateCacheWith", "Reusing %{public}s for %{public}s (DPL id %" PRIu64 ")", path.data(), headers["ETag"].data(), cacheId.value); helper->mapURL2UUID[path].cacheHit++; responses.emplace_back(Response{.id = cacheId, .size = helper->mapURL2UUID[path].size, .request = nullptr}); - allocator.adoptFromCache(output, cacheId, header::gSerializationMethodCCDB); + // allocator.adoptFromCache(output, cacheId, header::gSerializationMethodCCDB); // the outputBuffer was not used, can we destroy it? } O2_SIGNPOST_END(ccdb, sid, "populateCacheWith", "Finished populating cache with CCDB objects"); diff --git a/Framework/Core/CMakeLists.txt b/Framework/Core/CMakeLists.txt index 45af3ad6c59cc..d74eb45e49b92 100644 --- a/Framework/Core/CMakeLists.txt +++ b/Framework/Core/CMakeLists.txt @@ -160,7 +160,6 @@ o2_add_library(Framework src/DPLWebSocket.cxx src/StatusWebSocketHandler.cxx src/TimerParamSpec.cxx - test/TestClasses.cxx TARGETVARNAME targetName PRIVATE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_LIST_DIR}/src PUBLIC_LINK_LIBRARIES AliceO2::Configuration @@ -189,9 +188,16 @@ o2_add_library(Framework target_include_directories(${targetName} PUBLIC $) o2_target_root_dictionary(Framework + HEADERS include/Framework/StepTHn.h + LINKDEF src/StepTHnLinkDef.h) + +# o2::test::* support classes for unit tests, kept out of production libO2Framework. +o2_add_library(FrameworkTestSupport + SOURCES test/TestClasses.cxx + PUBLIC_LINK_LIBRARIES O2::Framework) +o2_target_root_dictionary(FrameworkTestSupport HEADERS test/TestClasses.h - include/Framework/StepTHn.h - LINKDEF test/FrameworkCoreTestLinkDef.h) + LINKDEF test/TestClassesLinkDef.h) add_executable(o2-test-framework-core test/test_AlgorithmSpec.cxx @@ -268,6 +274,7 @@ add_executable(o2-test-framework-core test/unittest_DataSpecUtils.cxx ) target_link_libraries(o2-test-framework-core PRIVATE O2::Framework) +target_link_libraries(o2-test-framework-core PRIVATE O2::FrameworkTestSupport) target_link_libraries(o2-test-framework-core PRIVATE O2::Catch2) get_filename_component(outdir ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/../tests ABSOLUTE) @@ -374,7 +381,6 @@ foreach(w RegionInfoCallbackService DanglingInputs DanglingOutputs - DataAllocator StaggeringWorkflow Forwarding ParallelPipeline @@ -403,6 +409,15 @@ foreach(w COMMAND_LINE_ARGS ${DPL_WORKFLOW_TESTS_EXTRA_OPTIONS} --run --shm-segment-size 20000000) endforeach() +o2_add_test(DataAllocator NAME test_Framework_test_DataAllocator + SOURCES test/test_DataAllocator.cxx + COMPONENT_NAME Framework + LABELS framework workflow + PUBLIC_LINK_LIBRARIES O2::Framework O2::FrameworkTestSupport + TIMEOUT 30 + NO_BOOST_TEST + COMMAND_LINE_ARGS ${DPL_WORKFLOW_TESTS_EXTRA_OPTIONS} --run --shm-segment-size 20000000) + if (BUILD_TESTING) # TODO: DanglingInput test not working for the moment [ERROR] Unable to relay # part. [WARN] Incoming data is already obsolete, not relaying. diff --git a/Framework/Core/include/Framework/ASoA.h b/Framework/Core/include/Framework/ASoA.h index a21532e42e692..c3918f23711b0 100644 --- a/Framework/Core/include/Framework/ASoA.h +++ b/Framework/Core/include/Framework/ASoA.h @@ -15,7 +15,7 @@ #if defined(__CLING__) #error "Please do not include this file in ROOT dictionary generation" #endif - +#include "Framework/Concepts.h" #include "Framework/ConcreteDataMatcher.h" #include "Framework/Pack.h" // IWYU pragma: export #include "Framework/FunctionalHelpers.h" // IWYU pragma: export @@ -28,6 +28,7 @@ #include "Framework/ArrowTableSlicingCache.h" // IWYU pragma: export #include "Framework/SliceCache.h" // IWYU pragma: export #include "Framework/VariantHelpers.h" // IWYU pragma: export +#include #include #include // IWYU pragma: export #include // IWYU pragma: export @@ -40,9 +41,17 @@ #include #include // IWYU pragma: export +namespace fair::mq::shmem +{ +struct MetaHeader; +} + namespace o2::framework { using ListVector = std::vector>; +#if (FAIRMQ_VERSION_DEC >= 111000) +using PointerReconstructor = std::function; +#endif std::string cutString(std::string&& str); std::string strToUpper(std::string&& str); @@ -58,6 +67,14 @@ void missingFilterDeclaration(int hash, int ai); void notBoundTable(const char* tableName); void* extractCCDBPayload(char* payload, size_t size, TClass const* cl, const char* what); +// ASCII-only lowercase. Column labels are plain identifiers, so we deliberately +// avoid the locale-aware std::tolower: it goes through the C locale facet on +// every character and dominated getIndexFromLabel in profiles. +constexpr char asciiToLower(char c) +{ + return (c >= 'A' && c <= 'Z') ? static_cast(c + 32) : c; +} + template auto createFieldsFromColumns(framework::pack) { @@ -189,30 +206,13 @@ consteval auto intersectOriginals() namespace o2::soa { -struct Binding; - -template -concept not_void = requires { !std::same_as; }; - -/// column identification concepts -template -concept is_persistent_column = requires(C c) { c.mColumnIterator; }; - +/// column identification template constexpr bool is_persistent_v = is_persistent_column; template using is_persistent_column_t = std::conditional_t, std::true_type, std::false_type>; -template -concept is_self_index_column = not_void && std::same_as; - -template -concept is_index_column = !is_self_index_column && requires(C c, o2::soa::Binding b) { - { c.setCurrentRaw(b) } -> std::same_as; - requires std::same_as; -}; - template using is_external_index_t = typename std::conditional_t, std::true_type, std::false_type>; @@ -239,6 +239,7 @@ static consteval int getIndexPosToKey_impl() /// Base type for table metadata template struct TableMetadata { + static constexpr void isTableMetadata() {}; using columns = framework::pack; using persistent_columns_t = framework::selected_pack; using external_index_columns_t = framework::selected_pack; @@ -270,6 +271,7 @@ struct TableMetadata { template struct MetadataTrait { + static constexpr void isMetadataTrait() {}; using metadata = void; }; @@ -277,6 +279,7 @@ struct MetadataTrait { /// type signature template struct Hash { + static constexpr void isHash() {}; static constexpr uint32_t hash = H; static constexpr char const* const str{""}; }; @@ -298,6 +301,7 @@ consteval auto filterForKey() #define O2HASH(_Str_) \ template <> \ struct Hash<_Str_ ""_h> { \ + static constexpr void isHash() {}; \ static constexpr uint32_t hash = _Str_ ""_h; \ static constexpr char const* const str{_Str_}; \ }; @@ -306,6 +310,8 @@ consteval auto filterForKey() #define O2ORIGIN(_Str_) \ template <> \ struct Hash<_Str_ ""_h> { \ + static constexpr void isHash() {}; \ + static constexpr void isOriginHash() {}; \ static constexpr header::DataOrigin origin{_Str_}; \ static constexpr uint32_t hash = _Str_ ""_h; \ static constexpr char const* const str{_Str_}; \ @@ -386,13 +392,6 @@ constexpr framework::ConcreteDataMatcher matcher() return {origin(), description(signature()), R.version}; } -/// hash identification concepts -template -concept is_aod_hash = requires(T t) { t.hash; t.str; }; - -template -concept is_origin_hash = is_aod_hash && requires(T t) { t.origin; }; - /// convert TableRef to a DPL source specification template static constexpr auto sourceSpec() @@ -446,27 +445,6 @@ struct Binding { using SelectionVector = std::vector; -template -concept has_parent_t = not_void; - -template -concept is_metadata = framework::base_of_template; - -template -concept is_metadata_trait = framework::specialization_of_template; - -template -concept has_metadata = is_metadata_trait && not_void; - -template -concept has_extension = is_metadata && not_void; - -template -concept has_configurable_extension = has_extension && requires(T t) { typename T::configurable_t; requires std::same_as; }; - -template -concept is_spawnable_column = std::same_as; - template struct EquivalentIndex { constexpr static bool value = false; @@ -533,13 +511,13 @@ class ColumnIterator : ChunkingPolicy : mColumn{column}, mCurrent{nullptr}, mCurrentPos{nullptr}, + mGlobalOffset{nullptr}, mLast{nullptr}, mFirstIndex{0}, - mCurrentChunk{0}, - mOffset{0} + mCurrentChunk{0} { auto array = getCurrentArray(); - mCurrent = reinterpret_cast const*>(array->values()->data()) + (mOffset >> SCALE_FACTOR); + mCurrent = reinterpret_cast const*>(array->values()->data()); mLast = mCurrent + array->length(); } @@ -555,10 +533,9 @@ class ColumnIterator : ChunkingPolicy { auto previousArray = getCurrentArray(); mFirstIndex += previousArray->length(); - mCurrentChunk++; auto array = getCurrentArray(); - mCurrent = reinterpret_cast const*>(array->values()->data()) + (mOffset >> SCALE_FACTOR) - (mFirstIndex >> SCALE_FACTOR); + mCurrent = reinterpret_cast const*>(array->values()->data()) - (mFirstIndex >> SCALE_FACTOR); mLast = mCurrent + array->length() + (mFirstIndex >> SCALE_FACTOR); } @@ -566,10 +543,9 @@ class ColumnIterator : ChunkingPolicy { auto previousArray = getCurrentArray(); mFirstIndex -= previousArray->length(); - mCurrentChunk--; auto array = getCurrentArray(); - mCurrent = reinterpret_cast const*>(array->values()->data()) + (mOffset >> SCALE_FACTOR) - (mFirstIndex >> SCALE_FACTOR); + mCurrent = reinterpret_cast const*>(array->values()->data()) - (mFirstIndex >> SCALE_FACTOR); mLast = mCurrent + array->length() + (mFirstIndex >> SCALE_FACTOR); } @@ -592,7 +568,7 @@ class ColumnIterator : ChunkingPolicy mCurrentChunk = mColumn->num_chunks() - 1; auto array = getCurrentArray(); mFirstIndex = mColumn->length() - array->length(); - mCurrent = reinterpret_cast const*>(array->values()->data()) + (mOffset >> SCALE_FACTOR) - (mFirstIndex >> SCALE_FACTOR); + mCurrent = reinterpret_cast const*>(array->values()->data()) - (mFirstIndex >> SCALE_FACTOR); mLast = mCurrent + array->length() + (mFirstIndex >> SCALE_FACTOR); } @@ -600,7 +576,7 @@ class ColumnIterator : ChunkingPolicy requires std::same_as> { checkSkipChunk(); - return (*(mCurrent - (mOffset >> SCALE_FACTOR) + ((*mCurrentPos + mOffset) >> SCALE_FACTOR)) & (1 << ((*mCurrentPos + mOffset) & 0x7))) != 0; + return (*(mCurrent + ((*mCurrentPos + *mGlobalOffset) >> SCALE_FACTOR)) & (1 << ((*mCurrentPos + *mGlobalOffset) & ((1 << SCALE_FACTOR) - 1)))) != 0; } auto operator*() const @@ -608,8 +584,8 @@ class ColumnIterator : ChunkingPolicy { checkSkipChunk(); auto list = std::static_pointer_cast(mColumn->chunk(mCurrentChunk)); - auto offset = list->value_offset(*mCurrentPos - mFirstIndex); - auto length = list->value_length(*mCurrentPos - mFirstIndex); + auto offset = list->value_offset(*mCurrentPos + *mGlobalOffset - mFirstIndex); + auto length = list->value_length(*mCurrentPos + *mGlobalOffset - mFirstIndex); return gsl::span const>{mCurrent + mFirstIndex + offset, mCurrent + mFirstIndex + (offset + length)}; } @@ -618,14 +594,14 @@ class ColumnIterator : ChunkingPolicy { checkSkipChunk(); auto array = std::static_pointer_cast(mColumn->chunk(mCurrentChunk)); - return array->GetView(*mCurrentPos - mFirstIndex); + return array->GetView(*mCurrentPos + *mGlobalOffset - mFirstIndex); } decltype(auto) operator*() const requires((!std::same_as>) && !std::same_as, arrow::ListArray> && !std::same_as, arrow::BinaryViewArray>) { checkSkipChunk(); - return *(mCurrent + (*mCurrentPos >> SCALE_FACTOR)); + return *(mCurrent + ((*mCurrentPos + *mGlobalOffset) >> SCALE_FACTOR)); } // Move to the chunk which containts element pos @@ -637,18 +613,18 @@ class ColumnIterator : ChunkingPolicy mutable unwrap_t const* mCurrent; int64_t const* mCurrentPos; + uint64_t const* mGlobalOffset; mutable unwrap_t const* mLast; arrow::ChunkedArray const* mColumn; mutable int mFirstIndex; mutable int mCurrentChunk; - mutable int mOffset; private: void checkSkipChunk() const requires((ChunkingPolicy::chunked == true) && std::same_as, arrow::ListArray>) { auto list = std::static_pointer_cast(mColumn->chunk(mCurrentChunk)); - if (O2_BUILTIN_UNLIKELY(*mCurrentPos - mFirstIndex >= list->length())) { + if (O2_BUILTIN_UNLIKELY(*mCurrentPos + *mGlobalOffset - mFirstIndex >= list->length())) { nextChunk(); } } @@ -656,7 +632,7 @@ class ColumnIterator : ChunkingPolicy void checkSkipChunk() const requires((ChunkingPolicy::chunked == true) && !std::same_as, arrow::ListArray>) { - if (O2_BUILTIN_UNLIKELY(((mCurrent + (*mCurrentPos >> SCALE_FACTOR)) >= mLast))) { + if (O2_BUILTIN_UNLIKELY(((mCurrent + ((*mCurrentPos + *mGlobalOffset) >> SCALE_FACTOR)) >= mLast))) { nextChunk(); } } @@ -670,7 +646,6 @@ class ColumnIterator : ChunkingPolicy requires(std::same_as, arrow::FixedSizeListArray>) { std::shared_ptr chunkToUse = mColumn->chunk(mCurrentChunk); - mOffset = chunkToUse->offset(); chunkToUse = std::dynamic_pointer_cast(chunkToUse)->values(); return std::static_pointer_cast>>(chunkToUse); } @@ -679,9 +654,7 @@ class ColumnIterator : ChunkingPolicy requires(std::same_as, arrow::ListArray>) { std::shared_ptr chunkToUse = mColumn->chunk(mCurrentChunk); - mOffset = chunkToUse->offset(); chunkToUse = std::dynamic_pointer_cast(chunkToUse)->values(); - mOffset = chunkToUse->offset(); return std::static_pointer_cast>>(chunkToUse); } @@ -689,13 +662,14 @@ class ColumnIterator : ChunkingPolicy requires(!std::same_as, arrow::FixedSizeListArray> && !std::same_as, arrow::ListArray>) { std::shared_ptr chunkToUse = mColumn->chunk(mCurrentChunk); - mOffset = chunkToUse->offset(); return std::static_pointer_cast>(chunkToUse); } }; template struct Column { + static constexpr void isIteratableColumn() {}; + using inherited_t = INHERIT; Column(ColumnIterator const& it) : mColumnIterator{it} @@ -730,6 +704,7 @@ struct Column { /// method call. template struct DynamicColumn { + static constexpr void isDynamicColumn() {}; using inherited_t = INHERIT; static constexpr const char* const& columnLabel() { return INHERIT::mLabel; } @@ -737,6 +712,7 @@ struct DynamicColumn { template struct IndexColumn { + static constexpr void isEnumeratingColumn() {}; using inherited_t = INHERIT; static constexpr const uint32_t hash = 0; @@ -745,6 +721,7 @@ struct IndexColumn { template struct MarkerColumn { + static constexpr void isMarkingColumn() {}; using inherited_t = INHERIT; static constexpr const uint32_t hash = 0; @@ -846,29 +823,6 @@ struct Index : o2::soa::IndexColumn> { std::tuple rowOffsets; }; -template -concept is_indexing_column = requires(C& c) { - c.rowIndices; - c.rowOffsets; -}; - -template -concept is_dynamic_column = requires(C& c) { - c.boundIterators; -}; - -template -concept is_marker_column = requires { &C::mark; }; - -template -using is_dynamic_t = std::conditional_t, std::true_type, std::false_type>; - -template -concept is_column = is_persistent_column || is_dynamic_column || is_indexing_column || is_marker_column; - -template -using is_indexing_t = std::conditional_t, std::true_type, std::false_type>; - struct IndexPolicyBase { /// Position inside the current table int64_t mRowIndex = 0; @@ -877,10 +831,12 @@ struct IndexPolicyBase { }; struct RowViewSentinel { + static constexpr void isRowViewSentinel() {}; int64_t const index; }; struct FilteredIndexPolicy : IndexPolicyBase { + static constexpr void isFilteredIndexPolicy(); // We use -1 in the IndexPolicyBase to indicate that the index is // invalid. What will validate the index is the this->setCursor() // which happens below which will properly setup the first index @@ -986,6 +942,7 @@ struct FilteredIndexPolicy : IndexPolicyBase { }; struct DefaultIndexPolicy : IndexPolicyBase { + static constexpr void isDefaultIndexPolicy() {}; /// Needed to be able to copy the policy DefaultIndexPolicy() = default; DefaultIndexPolicy(DefaultIndexPolicy&&) = default; @@ -1060,15 +1017,6 @@ struct DefaultIndexPolicy : IndexPolicyBase { int64_t mMaxRow = 0; }; -// template -// class Table; - -template -class Table; - -template -concept is_table = framework::specialization_of_template || framework::base_of_template; - /// Similar to a pair but not a pair, to avoid /// exposing the second type everywhere. template @@ -1077,17 +1025,13 @@ struct ColumnDataHolder { arrow::ChunkedArray* second; }; -template -concept can_bind = requires(T&& t) { - { t.B::mColumnIterator }; -}; - -template -concept has_index = (is_indexing_column || ...); +template +concept needs_ptr_rec = C::needs_ptr_rec; template struct TableIterator : IP, C... { public: + static constexpr void isTableIterator() {}; using self_t = TableIterator; using policy_t = IP; using all_columns = framework::pack; @@ -1252,6 +1196,21 @@ struct TableIterator : IP, C... { { doSetCurrentInternal(internal_index_columns_t{}, table); } +#if (FAIRMQ_VERSION_DEC >= 111000) + void setPointerReconstructor(framework::PointerReconstructor const& pointerReconstructor) + { + [&pointerReconstructor, this](framework::pack) { + ([&pointerReconstructor, this]() { + if constexpr (needs_ptr_rec) { + if (pointerReconstructor) { + CC::ptrRec = &pointerReconstructor; + } + } + }.template operator()(), + ...); + }(all_columns{}); + } +#endif private: /// Helper to move at the end of columns which actually have an iterator. @@ -1267,7 +1226,7 @@ struct TableIterator : IP, C... { { using namespace o2::soa; auto f = framework::overloaded{ - [this](T*) -> void { T::mColumnIterator.mCurrentPos = &this->mRowIndex; }, + [this](T*) -> void { T::mColumnIterator.mCurrentPos = &this->mRowIndex; T::mColumnIterator.mGlobalOffset = &this->mOffset; }, [this](T*) -> void { bindDynamicColumn(typename T::bindings_t{}); }, [this](T*) -> void {}, }; @@ -1295,7 +1254,6 @@ struct TableIterator : IP, C... { { static_assert(std::same_as(this)->mColumnIterator)), std::decay_t*>, "foo"); return &(static_cast(this)->mColumnIterator); - // return static_cast*>(nullptr); } template @@ -1306,52 +1264,14 @@ struct TableIterator : IP, C... { }; struct ArrowHelpers { - static std::shared_ptr joinTables(std::vector>&& tables); - static std::shared_ptr joinTables(std::vector>&& tables, std::span labels); - static std::shared_ptr joinTables(std::vector>&& tables, std::span labels); - static std::shared_ptr concatTables(std::vector>&& tables); -}; - -//! Helper to check if a type T is an iterator -template -concept is_iterator = framework::base_of_template || framework::specialization_of_template; - -template -concept is_table_or_iterator = is_table || is_iterator; - -template -concept with_originals = requires { - T::originals.size(); -}; - -template -concept with_sources = requires { - T::sources.size(); -}; - -template -concept with_sources_generator = requires(T t) { - t.template generateSources>(); -}; - -template -concept with_ccdb_urls = requires { - T::ccdb_urls.size(); -}; - -template -concept with_base_table = requires { - typename aod::MetadataTrait>::metadata::base_table_t; -}; - -template -concept with_expression_pack = requires { - typename T::expression_pack_t{}; -}; - -template -concept with_index_pack = requires { - typename T::index_pack_t{}; + static o2::soa::ArrowTableRef joinTables(std::vector>&& tables); + static o2::soa::ArrowTableRef joinTables(std::vector&& tables); + static o2::soa::ArrowTableRef joinTables(std::vector&& tables, std::span labels); + static o2::soa::ArrowTableRef joinTables(std::vector&& tables, std::span labels); + static o2::soa::ArrowTableRef joinTables(std::vector>&& tables, std::span labels); + static o2::soa::ArrowTableRef joinTables(std::vector>&& tables, std::span labels); + static o2::soa::ArrowTableRef concatTables(std::vector&& tables); + static o2::soa::ArrowTableRef concatTables(std::vector>&& tables); }; template os1, size_t N2, std::array os2> @@ -1376,12 +1296,6 @@ consteval bool is_binding_compatible_v() template using is_binding_compatible = std::conditional_t(), std::true_type, std::false_type>; -template -struct IndexTable; - -template -concept is_index_table = framework::specialization_of_template; - template static constexpr std::string getLabelForTable() { @@ -1420,8 +1334,8 @@ static constexpr auto hasColumnForKey(framework::pack, std::string_view ke return std::ranges::equal( str1, str2, [](char c1, char c2) { - return std::tolower(static_cast(c1)) == - std::tolower(static_cast(c2)); + return asciiToLower(static_cast(c1)) == + asciiToLower(static_cast(c2)); }); }; return (caseInsensitiveCompare(C::inherited_t::mLabel, key) || ...); @@ -1509,6 +1423,7 @@ namespace o2::framework { /// tracks origin in bindingKey matcher to handle the correct arguments struct PreslicePolicyBase { + static constexpr void isPreslicePolicy() {}; const std::string binding; Entry bindingKey; @@ -1520,12 +1435,7 @@ struct PreslicePolicySorted : public PreslicePolicyBase { void updateSliceInfo(SliceInfoPtr&& si); SliceInfoPtr sliceInfo; - std::shared_ptr getSliceFor(int value, std::shared_ptr const& input, uint64_t& offset) const; - // One-slot cache for the empty (0-row) slice, so that empty groups do not - // slice every column only to produce 0 rows (the common case for sparse - // grouping, e.g. candidates per collision). Keyed by the input table, which - // changes with every dataframe. - mutable std::pair> emptySlice{nullptr, nullptr}; + o2::soa::ArrowTableRef getSliceFor(int value, o2::soa::ArrowTableRef const& input) const; }; struct PreslicePolicyGeneral : public PreslicePolicyBase { @@ -1535,11 +1445,9 @@ struct PreslicePolicyGeneral : public PreslicePolicyBase { std::span getSliceFor(int value) const; }; -template -concept is_preslice_policy = std::derived_from; - template struct PresliceBase : public Policy { + static constexpr void isPresliceContainer() {}; constexpr static bool optional = OPT; using target_t = T; using policy_t = Policy; @@ -1550,14 +1458,14 @@ struct PresliceBase : public Policy { { } - std::shared_ptr getSliceFor(int value, std::shared_ptr const& input, uint64_t& offset) const + o2::soa::ArrowTableRef getSliceFor(int value, o2::soa::ArrowTableRef const& input) const { if constexpr (OPT) { if (Policy::isMissing()) { - return nullptr; + return {nullptr, {0, 0}}; } } - return Policy::getSliceFor(value, input, offset); + return Policy::getSliceFor(value, input); } std::span getSliceFor(int value) const @@ -1580,13 +1488,6 @@ using Preslice = PresliceBase; template using PresliceOptional = PresliceBase; -template -concept is_preslice = std::derived_from&& - requires(T) -{ - T::optional; -}; - /// Can be user to group together a number of Preslice declaration /// to avoid the limit of 100 data members per task /// @@ -1600,11 +1501,8 @@ concept is_preslice = std::derived_from&& /// /// preslices.perCol; struct PresliceGroup { + static constexpr void isPresliceGroup() {}; }; - -template -concept is_preslice_group = std::derived_from; - } // namespace o2::framework namespace o2::soa @@ -1614,25 +1512,10 @@ class FilteredBase; template class Filtered; -template -concept has_filtered_policy = not_void && std::same_as; - -template -concept is_filtered_iterator = is_iterator && has_filtered_policy; - -template -concept is_filtered_table = framework::base_of_template; - // FIXME: compatbility declaration to be removed template constexpr bool is_soa_filtered_v = is_filtered_table; -template -concept is_filtered = is_filtered_table || is_filtered_iterator; - -template -concept is_not_filtered_table = is_table && !is_filtered_table; - /// Helper function to extract bound indices template static consteval auto extractBindings(framework::pack) @@ -1651,9 +1534,8 @@ auto doSliceBy(T const* table, o2::framework::PresliceBase const missingOptionalPreslice(getLabelFromType>().data(), container.bindingKey.key.c_str()); } } - uint64_t offset = 0; - auto out = container.getSliceFor(value, table->asArrowTable(), offset); - auto t = typename T::self_t({out}, offset); + auto out = container.getSliceFor(value, table->asArrowTableRef()); + auto t = typename T::self_t({out}); if (t.tableSize() != 0) { table->copyIndexBindings(t); t.bindInternalIndicesTo(table); @@ -1664,7 +1546,7 @@ auto doSliceBy(T const* table, o2::framework::PresliceBase const template auto doSliceByHelper(T const* table, std::span const& selection) { - auto t = soa::Filtered({table->asArrowTable()}, selection); + auto t = soa::Filtered({table->asArrowTableRef()}, selection); if (t.tableSize() != 0) { table->copyIndexBindings(t); t.bindInternalIndicesTo(table); @@ -1677,7 +1559,7 @@ template requires(!soa::is_filtered_table) auto doSliceByHelper(T const* table, std::span const& selection) { - auto t = soa::Filtered({table->asArrowTable()}, selection); + auto t = soa::Filtered({table->asArrowTableRef()}, selection); if (t.tableSize() != 0) { table->copyIndexBindings(t); t.bindInternalIndicesTo(table); @@ -1701,17 +1583,17 @@ auto doSliceBy(T const* table, o2::framework::PresliceBase const SelectionVector sliceSelection(std::span const& mSelectedRows, int64_t nrows, uint64_t offset); template -auto prepareFilteredSlice(T const* table, std::shared_ptr slice, uint64_t offset) +auto prepareFilteredSlice(T const* table, o2::soa::ArrowTableRef slice) { - if (offset >= static_cast(table->tableSize())) { - Filtered fresult{{{slice}}, SelectionVector{}, 0}; + if (slice.range.offset >= static_cast(table->tableSize())) { + Filtered fresult{{slice}, SelectionVector{}}; if (fresult.tableSize() != 0) { table->copyIndexBindings(fresult); } return fresult; } - auto slicedSelection = sliceSelection(table->getSelectedRows(), slice->num_rows(), offset); - Filtered fresult{{{slice}}, std::move(slicedSelection), offset}; + auto slicedSelection = sliceSelection(table->getSelectedRows(), slice.range.size, slice.range.offset); + Filtered fresult{{slice}, std::move(slicedSelection)}; if (fresult.tableSize() != 0) { table->copyIndexBindings(fresult); } @@ -1727,9 +1609,8 @@ auto doFilteredSliceBy(T const* table, o2::framework::PresliceBase().data(), container.bindingKey.key.c_str()); } } - uint64_t offset = 0; - auto slice = container.getSliceFor(value, table->asArrowTable(), offset); - return prepareFilteredSlice(table, slice, offset); + auto slice = container.getSliceFor(value, table->asArrowTableRef()); + return prepareFilteredSlice(table, slice); } std::function originReplacement(header::DataOrigin newOrigin); @@ -1740,10 +1621,7 @@ auto doSliceByCached(T const* table, framework::expressions::BindingNode const& auto localCache = cache.ptr->getCacheFor({"", originReplacement(cache.ptr->newOrigin)(o2::soa::getMatcherFromTypeForKey(node.name)), node.name}); auto [offset, count] = localCache.getSliceFor(value); - // Empty group: reuse a cached empty (0-row) table instead of slicing every column. - auto slice = count == 0 ? cache.ptr->getEmptySliceFor(table->asArrowTable()) - : table->asArrowTable()->Slice(static_cast(offset), count); - auto t = typename T::self_t({slice}, static_cast(offset)); + auto t = typename T::self_t({table->asArrowTableRef().slice({static_cast(offset), count})}); if (t.tableSize() != 0) { table->copyIndexBindings(t); } @@ -1756,10 +1634,7 @@ auto doFilteredSliceByCached(T const* table, framework::expressions::BindingNode auto localCache = cache.ptr->getCacheFor({"", originReplacement(cache.ptr->newOrigin)(o2::soa::getMatcherFromTypeForKey(node.name)), node.name}); auto [offset, count] = localCache.getSliceFor(value); - // Empty group: reuse a cached empty (0-row) table instead of slicing every column. - auto slice = count == 0 ? cache.ptr->getEmptySliceFor(table->asArrowTable()) - : table->asArrowTable()->Slice(static_cast(offset), count); - return prepareFilteredSlice(table, slice, offset); + return prepareFilteredSlice(table, table->asArrowTableRef().slice({static_cast(offset), count})); } template @@ -1768,14 +1643,14 @@ auto doSliceByCachedUnsorted(T const* table, framework::expressions::BindingNode auto localCache = cache.ptr->getCacheUnsortedFor({"", originReplacement(cache.ptr->newOrigin)(o2::soa::getMatcherFromTypeForKey(node.name)), node.name}); if constexpr (soa::is_filtered_table) { - auto t = typename T::self_t({table->asArrowTable()}, localCache.getSliceFor(value)); + auto t = typename T::self_t({table->asArrowTableRef()}, localCache.getSliceFor(value)); if (t.tableSize() != 0) { t.intersectWithSelection(table->getSelectedRows()); table->copyIndexBindings(t); } return t; } else { - auto t = Filtered({table->asArrowTable()}, localCache.getSliceFor(value)); + auto t = Filtered({table->asArrowTableRef()}, localCache.getSliceFor(value)); if (t.tableSize() != 0) { table->copyIndexBindings(t); } @@ -1786,7 +1661,7 @@ auto doSliceByCachedUnsorted(T const* table, framework::expressions::BindingNode template auto select(T const& t, framework::expressions::Filter const& f) { - return Filtered({t.asArrowTable()}, selectionToVector(framework::expressions::createSelection(t.asArrowTable(), f))); + return Filtered({t.asArrowTableRef()}, selectionToVector(framework::expressions::createSelection(t.asArrowTable(), f))); } arrow::ChunkedArray* getIndexFromLabel(arrow::Table* table, std::string_view label); @@ -1795,7 +1670,6 @@ template consteval auto base_iter(framework::pack&&) -> TableIterator { } - template requires((sizeof...(Ts) > 0) && (soa::is_column && ...)) consteval auto getColumns() @@ -1844,6 +1718,7 @@ template ; using table_t = self_t; @@ -1852,7 +1727,7 @@ class Table static constexpr const auto originalLabels = [] refs, size_t... Is>(std::index_sequence) { return std::array{o2::aod::label()...}; }.template operator()(std::make_index_sequence()); - static constexpr const uint32_t binding_origin = originals[0].origin_hash; // commonOrigin(); + static constexpr const uint32_t binding_origin = originals[0].origin_hash; static constexpr header::DataOrigin binding_origin_ = o2::aod::Hash::origin; template bindings> @@ -1900,7 +1775,6 @@ class Table using columns_t = typename Parent::columns_t; using external_index_columns_t = typename Parent::external_index_columns_t; using bindings_pack_t = decltype([](framework::pack) -> framework::pack {}(external_index_columns_t{})); - // static constexpr const std::array originals{T::ref...}; static constexpr auto originals = Parent::originals; using policy_t = IP; using parent_t = Parent; @@ -2052,8 +1926,7 @@ class Table using iterator_template = TableIteratorBase; template - static consteval auto full_iter() - { + using iterator_template_o = decltype([]() { if constexpr (sizeof...(Ts) == 0) { return iterator_template{}; } else { @@ -2063,10 +1936,7 @@ class Table return iterator_template{}; } } - } - - template - using iterator_template_o = decltype(full_iter()); + }()); using iterator = iterator_template_o; using filtered_iterator = iterator_template_o; @@ -2080,12 +1950,11 @@ class Table return [](framework::pack) { return std::set{{C::hash...}}; }(columns_t{}); } - Table(std::shared_ptr table, uint64_t offset = 0) - : mTable(table), - mOffset(offset), - mEnd{table->num_rows()} + Table(o2::soa::ArrowTableRef tableRef) + : mArrowTableRef(tableRef), + mEnd{tableRef.range.size} { - if (mTable->num_rows() == 0) { + if (mArrowTableRef.tablePtr->num_rows() == 0) { for (size_t ci = 0; ci < framework::pack_size(columns_t{}); ++ci) { mColumnChunks[ci] = nullptr; } @@ -2095,20 +1964,37 @@ class Table for (size_t ci = 0; ci < framework::pack_size(columns_t{}); ++ci) { mColumnChunks[ci] = lookups[ci]; } - mBegin = unfiltered_iterator{mColumnChunks, {table->num_rows(), offset}}; + mBegin = unfiltered_iterator{mColumnChunks, {mEnd.index, mArrowTableRef.range.offset}}; mBegin.bindInternalIndices(this); } } - Table(std::vector>&& tables, uint64_t offset = 0) + Table(std::shared_ptr table) + : Table(o2::soa::ArrowTableRef{table}) + { + } + + Table(std::vector&& tables) requires(ref.origin_hash != "CONC"_h) - : Table(ArrowHelpers::joinTables(std::move(tables), std::span{originalLabels}), offset) + : Table(ArrowHelpers::joinTables(std::forward>(tables), std::span{originalLabels})) { } - Table(std::vector>&& tables, uint64_t offset = 0) + Table(std::vector&& tables) requires(ref.origin_hash == "CONC"_h) - : Table(ArrowHelpers::concatTables(std::move(tables)), offset) + : Table(ArrowHelpers::concatTables(std::forward>(tables))) + { + } + + Table(std::vector>&& tables) + requires(ref.origin_hash != "CONC"_h) + : Table(ArrowHelpers::joinTables(std::forward>>(tables))) + { + } + + Table(std::vector>&& tables) + requires(ref.origin_hash == "CONC"_h) + : Table(ArrowHelpers::concatTables(std::forward>>(tables))) { } @@ -2158,7 +2044,7 @@ class Table // is held by the table, so we are safe passing the bare pointer. If it does it // means that the iterator on a table is outliving the table itself, which is // a bad idea. - return filtered_iterator(mColumnChunks, {selection, mTable->num_rows(), mOffset}); + return filtered_iterator(mColumnChunks, {selection, mArrowTableRef.tablePtr->num_rows(), mArrowTableRef.range.offset}); } iterator iteratorAt(uint64_t i) const @@ -2186,17 +2072,27 @@ class Table /// Return a type erased arrow table backing store for / the type safe table. [[nodiscard]] std::shared_ptr asArrowTable() const { - return mTable; + return mArrowTableRef.tablePtr; + } + + [[nodiscard]] std::shared_ptr asArrowTableConstrained() const + { + return mArrowTableRef.tablePtr->Slice(mArrowTableRef.range.offset, mArrowTableRef.range.size); + } + + [[nodiscard]] ArrowTableRef asArrowTableRef() const + { + return mArrowTableRef; } /// Return offset auto offset() const { - return mOffset; + return mArrowTableRef.range.offset; } /// Size of the table, in rows. [[nodiscard]] int64_t size() const { - return mTable->num_rows(); + return mArrowTableRef.range.size; } [[nodiscard]] int64_t tableSize() const @@ -2282,27 +2178,35 @@ class Table auto rawSlice(uint64_t start, uint64_t end) const { - return self_t{mTable->Slice(start, end - start + 1), start}; + return self_t{mArrowTableRef.slice({start, static_cast(end - start + 1)})}; } auto emptySlice() const { - return self_t{mTable->Slice(0, 0), 0}; + return self_t{mArrowTableRef.makeEmpty()}; } - +#if (FAIRMQ_VERSION_DEC >= 111000) + void setPointerReconstructor(framework::PointerReconstructor const& pointerReconstructor) + { + mBegin.setPointerReconstructor(pointerReconstructor); + } +#endif private: template arrow::ChunkedArray* lookupColumn() { - if constexpr (soa::is_persistent_column) { - auto label = T::columnLabel(); - return getIndexFromLabel(mTable.get(), label); - } else { - return nullptr; - } + return nullptr; } - std::shared_ptr mTable = nullptr; - uint64_t mOffset = 0; + + template + arrow::ChunkedArray* lookupColumn() + { + return getIndexFromLabel(mArrowTableRef.tablePtr.get(), T::columnLabel()); + } + + ArrowTableRef mArrowTableRef; + // std::shared_ptr mTable = nullptr; + // uint64_t mOffset = 0; // Cached pointers to the ChunkedArray associated to a column arrow::ChunkedArray* mColumnChunks[framework::pack_size(columns_t{})]; RowViewSentinel mEnd; @@ -2337,7 +2241,7 @@ concept dynamic_with_common_getter = is_dynamic_column && }; template -concept persistent_with_common_getter = is_persistent_v && requires(T t) { +concept persistent_with_common_getter = is_persistent_column && requires(T t) { { t.get() } -> std::convertible_to; }; @@ -2405,13 +2309,13 @@ namespace o2::aod O2ORIGIN("AOD"); O2ORIGIN("AOD1"); O2ORIGIN("AOD2"); -// O2ORIGIN("DYN"); -// O2ORIGIN("IDX"); -// O2ORIGIN("ATIM"); + O2ORIGIN("JOIN"); O2HASH("JOIN/0"); + O2ORIGIN("CONC"); O2HASH("CONC/0"); + O2ORIGIN("TEST"); O2HASH("TEST/0"); } // namespace o2::aod @@ -2468,6 +2372,57 @@ consteval static std::string_view namespace_prefix() }; \ [[maybe_unused]] static constexpr o2::framework::expressions::BindingNode _Getter_ { _Label_, _Name_::hash, o2::framework::expressions::selectArrowType<_Type_>() } +#if (FAIRMQ_VERSION_DEC >= 111000) +#define DECLARE_SOA_CCDB_COLUMN_FULL(_Name_, _Label_, _Getter_, _ConcreteType_, _CCDBQuery_) \ + struct _Name_ : o2::soa::Column { \ + static constexpr const char* mLabel = _Label_; \ + static constexpr const char* query = _CCDBQuery_; \ + static constexpr const uint32_t hash = crc32(namespace_prefix<_Name_>(), std::string_view{#_Getter_}); \ + static constexpr bool needs_ptr_rec = true; \ + std::function const* ptrRec = nullptr; \ + using base = o2::soa::Column; \ + using type = int64_t[3]; \ + using column_t = _Name_; \ + _Name_(arrow::ChunkedArray const* column) \ + : o2::soa::Column(o2::soa::ColumnIterator(column)) \ + { \ + } \ + \ + _Name_() = default; \ + _Name_(_Name_ const& other) = default; \ + _Name_& operator=(_Name_ const& other) = default; \ + \ + decltype(auto) _Getter_() const \ + { \ + auto& [handle, segment, size] = *mColumnIterator; \ + auto span = std::span{(*ptrRec)(fair::mq::shmem::MetaHeader{ \ + static_cast(size), \ + 0, handle, 0, 0, \ + static_cast(segment), true}), \ + static_cast(size)}; \ + if constexpr (std::same_as<_ConcreteType_, std::span>) { \ + return span; \ + } else { \ + static std::byte* payload = nullptr; \ + static _ConcreteType_* deserialised = nullptr; \ + static TClass* c = TClass::GetClass(#_ConcreteType_); \ + if (payload != (std::byte*)span.data()) { \ + payload = (std::byte*)span.data(); \ + delete deserialised; \ + TBufferFile f(TBufferFile::EMode::kRead, span.size(), (char*)span.data(), kFALSE); \ + deserialised = (_ConcreteType_*)soa::extractCCDBPayload((char*)payload, span.size(), c, "ccdb_object"); \ + } \ + return *deserialised; \ + } \ + } \ + \ + decltype(auto) \ + get() const \ + { \ + return _Getter_(); \ + } \ + }; +#else #define DECLARE_SOA_CCDB_COLUMN_FULL(_Name_, _Label_, _Getter_, _ConcreteType_, _CCDBQuery_) \ struct _Name_ : o2::soa::Column, _Name_> { \ static constexpr const char* mLabel = _Label_; \ @@ -2510,6 +2465,7 @@ consteval static std::string_view namespace_prefix() return _Getter_(); \ } \ }; +#endif #define DECLARE_SOA_CCDB_COLUMN(_Name_, _Getter_, _ConcreteType_, _CCDBQuery_) \ DECLARE_SOA_CCDB_COLUMN_FULL(_Name_, "f" #_Name_, _Getter_, _ConcreteType_, _CCDBQuery_) @@ -3241,6 +3197,7 @@ consteval auto getIndexTargets() #define DECLARE_SOA_TABLE_METADATA_TRAIT(_Name_, _Desc_, _Version_) \ template <> \ struct MetadataTrait> { \ + static constexpr void isMetadataTrait() {}; \ using metadata = _Name_##Metadata; \ }; @@ -3251,6 +3208,7 @@ consteval auto getIndexTargets() using _Name_ = _Name_##From>; \ template <> \ struct MetadataTrait> { \ + static constexpr void isMetadataTrait() {}; \ using metadata = _Name_##Metadata; \ }; @@ -3310,6 +3268,7 @@ consteval auto getIndexTargets() }; \ template <> \ struct MetadataTrait> { \ + static constexpr void isMetadataTrait() {}; \ using metadata = _Name_##ExtensionMetadata; \ }; \ template \ @@ -3344,6 +3303,7 @@ consteval auto getIndexTargets() }; \ template <> \ struct MetadataTrait> { \ + static constexpr void isMetadataTrait() {}; \ using metadata = _Name_##CfgExtensionMetadata; \ }; \ template \ @@ -3379,6 +3339,7 @@ consteval auto getIndexTargets() }; \ template <> \ struct MetadataTrait> { \ + static constexpr void isMetadataTrait() {}; \ using metadata = _Name_##Metadata; \ }; \ template \ @@ -3433,6 +3394,7 @@ consteval auto getIndexTargets() }; \ template <> \ struct MetadataTrait> { \ + static constexpr void isMetadataTrait() {}; \ using metadata = _Name_##TimestampMetadata; \ }; \ template \ @@ -3449,22 +3411,17 @@ namespace o2::soa { template struct Join : Table, o2::aod::Hash<"JOIN/0"_h>, o2::aod::Hash<"JOIN"_h>, Ts...> { + static constexpr void isJoin() {}; using base = Table, o2::aod::Hash<"JOIN/0"_h>, o2::aod::Hash<"JOIN"_h>, Ts...>; - Join(std::shared_ptr&& table, uint64_t offset = 0) - : base{std::move(table), offset} - { - if (this->tableSize() != 0) { - bindInternalIndicesTo(this); - } - } - Join(std::vector>&& tables, uint64_t offset = 0) - : base{ArrowHelpers::joinTables(std::move(tables), std::span{base::originalLabels}), offset} + Join(std::vector&& tables) + : base{ArrowHelpers::joinTables(std::move(tables))} { if (this->tableSize() != 0) { bindInternalIndicesTo(this); } } + using base::bindExternalIndices; using base::bindInternalIndicesTo; static constexpr const uint32_t binding_origin = base::binding_origin; @@ -3534,12 +3491,12 @@ struct Join : Table, o2::aod::Hash<"JOIN/0"_h>, o2::aod: auto rawSlice(uint64_t start, uint64_t end) const { - return self_t{{this->asArrowTable()->Slice(start, end - start + 1)}, start}; + return self_t{{this->asArrowTableRef().slice({start, static_cast(end - start + 1)})}}; } auto emptySlice() const { - return self_t{{this->asArrowTable()->Slice(0, 0)}, 0}; + return self_t{{this->asArrowTableRef().slice({0, 0})}}; } template @@ -3554,12 +3511,9 @@ struct Join : Table, o2::aod::Hash<"JOIN/0"_h>, o2::aod: template constexpr auto join(Ts const&... t) { - return Join(ArrowHelpers::joinTables({t.asArrowTable()...}, std::span{Join::base::originalLabels})); + return Join({ArrowHelpers::joinTables({t.asArrowTableRef()...}, std::span{Join::base::originalLabels})}); } -template -concept is_join = framework::specialization_of_template; - template constexpr bool is_soa_join_v = is_join; @@ -3567,15 +3521,26 @@ template struct Concat : Table, o2::aod::Hash<"CONC/0"_h>, o2::aod::Hash<"CONC"_h>, Ts...> { using base = Table, o2::aod::Hash<"CONC/0"_h>, o2::aod::Hash<"CONC"_h>, Ts...>; using self_t = Concat; - Concat(std::vector>&& tables, uint64_t offset = 0) - : base{ArrowHelpers::concatTables(std::move(tables)), offset} + + Concat(ArrowTableRef table) + : base{table} { bindInternalIndicesTo(this); } - Concat(Ts const&... t, uint64_t offset = 0) - : base{ArrowHelpers::concatTables({t.asArrowTable()...}), offset} + + Concat(std::shared_ptr table) + : Concat{ArrowTableRef{table}} + { + } + + Concat(std::vector&& tables) + : Concat{ArrowHelpers::concatTables(std::move(tables))} + { + } + + Concat(Ts const&... t) + : Concat{ArrowHelpers::concatTables({t.asArrowTableRef()...})} { - bindInternalIndicesTo(this); } using base::originals; @@ -3601,10 +3566,14 @@ constexpr auto concat(Ts const&... t) return Concat{t...}; } +template +concept is_a_selection = std::same_as, gandiva::Selection> || std::same_as, SelectionVector> || std::same_as, std::span>; + template class FilteredBase : public T { public: + static constexpr void isFilteredBase() {}; using self_t = FilteredBase; using table_t = typename T::table_t; using T::originals; @@ -3629,34 +3598,10 @@ class FilteredBase : public T using unfiltered_iterator = T::template iterator_template_o; using const_iterator = iterator; - FilteredBase(std::vector>&& tables, gandiva::Selection const& selection, uint64_t offset = 0) - : T{std::move(tables), offset}, - mSelectedRows{getSpan(selection)} - { - if (this->tableSize() != 0) { - mFilteredBegin = table_t::filtered_begin(mSelectedRows); - } - resetRanges(); - mFilteredBegin.bindInternalIndices(this); - } - - FilteredBase(std::vector>&& tables, SelectionVector&& selection, uint64_t offset = 0) - : T{std::move(tables), offset}, - mSelectedRowsCache{std::move(selection)}, - mCached{true} - { - mSelectedRows = std::span{mSelectedRowsCache}; - if (this->tableSize() != 0) { - mFilteredBegin = table_t::filtered_begin(mSelectedRows); - } - resetRanges(); - mFilteredBegin.bindInternalIndices(this); - } - - FilteredBase(std::vector>&& tables, std::span const& selection, uint64_t offset = 0) - : T{std::move(tables), offset}, - mSelectedRows{selection} + FilteredBase(std::vector&& tables, is_a_selection auto selection) + : T{std::move(tables)} { + adoptSelection(selection); if (this->tableSize() != 0) { mFilteredBegin = table_t::filtered_begin(mSelectedRows); } @@ -3708,7 +3653,7 @@ class FilteredBase : public T [[nodiscard]] int64_t tableSize() const { - return table_t::asArrowTable()->num_rows(); + return this->asArrowTableRef().range.size; } auto const& getSelectedRows() const @@ -3721,12 +3666,12 @@ class FilteredBase : public T SelectionVector newSelection; newSelection.resize(static_cast(end - start + 1)); std::iota(newSelection.begin(), newSelection.end(), start); - return self_t{{this->asArrowTable()}, std::move(newSelection), 0}; + return self_t{{this->asArrowTableRef()}, std::move(newSelection)}; } auto emptySlice() const { - return self_t{{this->asArrowTable()}, SelectionVector{}, 0}; + return self_t{{this->asArrowTableRef()}, SelectionVector{}}; } static inline auto getSpan(gandiva::Selection const& sel) @@ -3809,41 +3754,21 @@ class FilteredBase : public T return static_cast(std::distance(mSelectedRows.begin(), locate)); } - void sumWithSelection(SelectionVector const& selection) + void sumWithSelection(is_a_selection auto selection) { mCached = true; SelectionVector rowsUnion; - std::set_union(mSelectedRows.begin(), mSelectedRows.end(), selection.begin(), selection.end(), std::back_inserter(rowsUnion)); + std::ranges::set_union(mSelectedRows, selection, std::back_inserter(rowsUnion)); mSelectedRowsCache.clear(); mSelectedRowsCache = rowsUnion; resetRanges(); } - void intersectWithSelection(SelectionVector const& selection) + void intersectWithSelection(is_a_selection auto selection) { mCached = true; SelectionVector intersection; - std::set_intersection(mSelectedRows.begin(), mSelectedRows.end(), selection.begin(), selection.end(), std::back_inserter(intersection)); - mSelectedRowsCache.clear(); - mSelectedRowsCache = intersection; - resetRanges(); - } - - void sumWithSelection(std::span const& selection) - { - mCached = true; - SelectionVector rowsUnion; - std::set_union(mSelectedRows.begin(), mSelectedRows.end(), selection.begin(), selection.end(), std::back_inserter(rowsUnion)); - mSelectedRowsCache.clear(); - mSelectedRowsCache = rowsUnion; - resetRanges(); - } - - void intersectWithSelection(std::span const& selection) - { - mCached = true; - SelectionVector intersection; - std::set_intersection(mSelectedRows.begin(), mSelectedRows.end(), selection.begin(), selection.end(), std::back_inserter(intersection)); + std::ranges::set_intersection(mSelectedRows, selection, std::back_inserter(intersection)); mSelectedRowsCache.clear(); mSelectedRowsCache = intersection; resetRanges(); @@ -3853,7 +3778,12 @@ class FilteredBase : public T { return mCached; } - +#if (FAIRMQ_VERSION_DEC >= 111000) + void setPointerReconstructor(framework::PointerReconstructor const& pointerReconstructor) + { + mFilteredBegin.setPointerReconstructor(pointerReconstructor); + } +#endif private: void resetRanges() { @@ -3868,6 +3798,36 @@ class FilteredBase : public T } } + template + inline void adoptSelection(S) + { + } + + template + requires(std::same_as, gandiva::Selection>) + inline void adoptSelection(S selection) + { + mSelectedRows = getSpan(selection); + mCached = false; + } + + template + requires(std::same_as, SelectionVector>) + inline void adoptSelection(S selection) + { + mSelectedRowsCache = std::move(selection); + mSelectedRows = std::span{mSelectedRowsCache}; + mCached = true; + } + + template + requires(std::same_as, std::span>) + inline void adoptSelection(S selection) + { + mSelectedRows = selection; + mCached = false; + } + std::span mSelectedRows; SelectionVector mSelectedRowsCache; bool mCached = false; @@ -3898,23 +3858,10 @@ class Filtered : public FilteredBase return const_iterator(this->cached_begin()); } - Filtered(std::vector>&& tables, gandiva::Selection const& selection, uint64_t offset = 0) - : FilteredBase(std::move(tables), selection, offset) {} - - Filtered(std::vector>&& tables, SelectionVector&& selection, uint64_t offset = 0) - : FilteredBase(std::move(tables), std::forward(selection), offset) {} + Filtered(std::vector&& tables, is_a_selection auto selection) + : FilteredBase{std::move(tables), std::forward(selection)} {} - Filtered(std::vector>&& tables, std::span const& selection, uint64_t offset = 0) - : FilteredBase(std::move(tables), selection, offset) {} - - Filtered operator+(SelectionVector const& selection) - { - Filtered copy(*this); - copy.sumWithSelection(selection); - return copy; - } - - Filtered operator+(std::span const& selection) + Filtered operator+(is_a_selection auto selection) { Filtered copy(*this); copy.sumWithSelection(selection); @@ -3926,13 +3873,7 @@ class Filtered : public FilteredBase return operator+(other.getSelectedRows()); } - Filtered operator+=(SelectionVector const& selection) - { - this->sumWithSelection(selection); - return *this; - } - - Filtered operator+=(std::span const& selection) + Filtered operator+=(is_a_selection auto selection) { this->sumWithSelection(selection); return *this; @@ -3943,14 +3884,7 @@ class Filtered : public FilteredBase return operator+=(other.getSelectedRows()); } - Filtered operator*(SelectionVector const& selection) - { - Filtered copy(*this); - copy.intersectWithSelection(selection); - return copy; - } - - Filtered operator*(std::span const& selection) + Filtered operator*(is_a_selection auto selection) { Filtered copy(*this); copy.intersectWithSelection(selection); @@ -3962,13 +3896,7 @@ class Filtered : public FilteredBase return operator*(other.getSelectedRows()); } - Filtered operator*=(SelectionVector const& selection) - { - this->intersectWithSelection(selection); - return *this; - } - - Filtered operator*=(std::span const& selection) + Filtered operator*=(is_a_selection auto selection) { this->intersectWithSelection(selection); return *this; @@ -3993,12 +3921,12 @@ class Filtered : public FilteredBase SelectionVector newSelection; newSelection.resize(static_cast(end - start + 1)); std::iota(newSelection.begin(), newSelection.end(), start); - return self_t{{this->asArrowTable()}, std::move(newSelection), 0}; + return self_t{{this->asArrowTableRef()}, std::move(newSelection)}; } auto emptySlice() const { - return self_t{{this->asArrowTable()}, SelectionVector{}, 0}; + return self_t{{this->asArrowTableRef()}, SelectionVector{}}; } template @@ -4060,38 +3988,15 @@ class Filtered> : public FilteredBase return const_iterator(this->cached_begin()); } - Filtered(std::vector>&& tables, gandiva::Selection const& selection, uint64_t offset = 0) - : FilteredBase(std::move(extractTablesFromFiltered(tables)), selection, offset) + Filtered(std::vector>&& tables, is_a_selection auto selection) + : FilteredBase(std::move(extractTablesFromFiltered(tables)), std::forward(selection)) { for (auto& table : tables) { *this *= table; } } - Filtered(std::vector>&& tables, SelectionVector&& selection, uint64_t offset = 0) - : FilteredBase(std::move(extractTablesFromFiltered(tables)), std::forward(selection), offset) - { - for (auto& table : tables) { - *this *= table; - } - } - - Filtered(std::vector>&& tables, std::span const& selection, uint64_t offset = 0) - : FilteredBase(std::move(extractTablesFromFiltered(tables)), selection, offset) - { - for (auto& table : tables) { - *this *= table; - } - } - - Filtered> operator+(SelectionVector const& selection) - { - Filtered> copy(*this); - copy.sumWithSelection(selection); - return copy; - } - - Filtered> operator+(std::span const& selection) + Filtered> operator+(is_a_selection auto selection) { Filtered> copy(*this); copy.sumWithSelection(selection); @@ -4103,13 +4008,7 @@ class Filtered> : public FilteredBase return operator+(other.getSelectedRows()); } - Filtered> operator+=(SelectionVector const& selection) - { - this->sumWithSelection(selection); - return *this; - } - - Filtered> operator+=(std::span const& selection) + Filtered> operator+=(is_a_selection auto selection) { this->sumWithSelection(selection); return *this; @@ -4120,14 +4019,7 @@ class Filtered> : public FilteredBase return operator+=(other.getSelectedRows()); } - Filtered> operator*(SelectionVector const& selection) - { - Filtered> copy(*this); - copy.intersectionWithSelection(selection); - return copy; - } - - Filtered> operator*(std::span const& selection) + Filtered> operator*(is_a_selection auto selection) { Filtered> copy(*this); copy.intersectionWithSelection(selection); @@ -4139,13 +4031,7 @@ class Filtered> : public FilteredBase return operator*(other.getSelectedRows()); } - Filtered> operator*=(SelectionVector const& selection) - { - this->intersectWithSelection(selection); - return *this; - } - - Filtered> operator*=(std::span const& selection) + Filtered> operator*=(is_a_selection auto selection) { this->intersectWithSelection(selection); return *this; @@ -4168,12 +4054,12 @@ class Filtered> : public FilteredBase SelectionVector newSelection; newSelection.resize(static_cast(end - start + 1)); std::iota(newSelection.begin(), newSelection.end(), start); - return self_t{{this->asArrowTable()}, std::move(newSelection), 0}; + return self_t{{this->asArrowTableRef()}, std::move(newSelection)}; } auto emptySlice() const { - return self_t{{this->asArrowTable()}, SelectionVector{}, 0}; + return self_t{{this->asArrowTableRef()}, SelectionVector{}}; } auto sliceByCached(framework::expressions::BindingNode const& node, int value, o2::framework::SliceCache& cache) const @@ -4199,11 +4085,11 @@ class Filtered> : public FilteredBase } private: - std::vector> extractTablesFromFiltered(std::vector>& tables) + std::vector extractTablesFromFiltered(std::vector>& tables) { - std::vector> outTables; + std::vector outTables; for (auto& table : tables) { - outTables.push_back(table.asArrowTable()); + outTables.push_back(table.asArrowTableRef()); } return outTables; } @@ -4216,6 +4102,7 @@ class Filtered> : public FilteredBase /// First index will be used by process() as the grouping template struct IndexTable : Table { + static constexpr void isIndexTable() {}; using self_t = IndexTable; using base_t = Table; using table_t = base_t; @@ -4238,15 +4125,13 @@ struct IndexTable : Table { ...); } - IndexTable(std::shared_ptr table, uint64_t offset = 0) - : base_t{table, offset} - { - } + IndexTable(ArrowTableRef table) + : base_t{table} {} - IndexTable(std::vector> tables, uint64_t offset = 0) - : base_t{tables[0], offset} - { - } + /// FIXME: this is a compatiblity for a generic constructor call with a vector + /// there has to be a safer way + IndexTable(std::vector&& tables) + : base_t{tables[0]} {} IndexTable(IndexTable const&) = default; IndexTable(IndexTable&&) = default; @@ -4261,15 +4146,11 @@ struct IndexTable : Table { template struct SmallGroupsBase : public Filtered { + static constexpr void isSmallGroups() {}; static constexpr bool applyFilters = APPLY; - SmallGroupsBase(std::vector>&& tables, gandiva::Selection const& selection, uint64_t offset = 0) - : Filtered(std::move(tables), selection, offset) {} - - SmallGroupsBase(std::vector>&& tables, SelectionVector&& selection, uint64_t offset = 0) - : Filtered(std::move(tables), std::forward(selection), offset) {} - SmallGroupsBase(std::vector>&& tables, std::span const& selection, uint64_t offset = 0) - : Filtered(std::move(tables), selection, offset) {} + SmallGroupsBase(std::vector&& tables, is_a_selection auto selection) + : Filtered(std::move(tables), selection) {} }; template @@ -4277,11 +4158,6 @@ using SmallGroups = SmallGroupsBase; template using SmallGroupsUnfiltered = SmallGroupsBase; - -template -concept is_smallgroups = requires { - [](SmallGroupsBase*) {}(std::declval*>()); -}; } // namespace o2::soa #endif // O2_FRAMEWORK_ASOA_H_ diff --git a/Framework/Core/include/Framework/AnalysisDataModel.h b/Framework/Core/include/Framework/AnalysisDataModel.h index c8dd33fba62ee..40bc8f651098a 100644 --- a/Framework/Core/include/Framework/AnalysisDataModel.h +++ b/Framework/Core/include/Framework/AnalysisDataModel.h @@ -26,6 +26,11 @@ #include "SimulationDataFormat/MCGenProperties.h" #include "Framework/PID.h" +#include +#if (FAIRMQ_VERSION_DEC >= 111000) +#include +#endif + namespace o2 { namespace aod @@ -1984,34 +1989,52 @@ DECLARE_SOA_EXPRESSION_COLUMN(Y, y, float, //! Particle rapidity, conditionally (aod::mcparticle::e - aod::mcparticle::pz)))); } // namespace mcparticle +namespace mcparticle_v2 +{ +// for improved getters with protection against incorrect physical primary tagging +// note: this has to be declared in a separate namespace so it does not conflict with existing +// derived data table declarations in O2Physics +DECLARE_SOA_DYNAMIC_COLUMN(IsPhysicalPrimary, isPhysicalPrimary, //! True if particle is considered a physical primary according to the ALICE definition + [](uint8_t input_flags, float vx, float vy) -> bool { return (o2::aod::mcparticle::Tools::removeIsPhysicalPrimaryBit(input_flags, vx, vy) & o2::aod::mcparticle::enums::PhysicalPrimary) == o2::aod::mcparticle::enums::PhysicalPrimary; }); + +// avoid that the stored flags are provided unprotected via +// the getter '.flags': analysers will get the correct bit map transparently +DECLARE_SOA_COLUMN(Flags, storedFlags, uint8_t); //! ALICE specific flags, see MCParticleFlags. Do not use directly. Use the dynamic columns, e.g. producedByGenerator() +DECLARE_SOA_DYNAMIC_COLUMN(ProtectedFlags, flags, //! protected against + [](uint8_t input_flags, float vx, float vy) -> uint8_t { return o2::aod::mcparticle::Tools::removeIsPhysicalPrimaryBit(input_flags, vx, vy); }); + +} // namespace mcparticle_v2 + DECLARE_SOA_TABLE_FULL(StoredMcParticles_000, "McParticles", "AOD", "MCPARTICLE", //! MC particle table, version 000 o2::soa::Index<>, mcparticle::McCollisionId, - mcparticle::PdgCode, mcparticle::StatusCode, mcparticle::Flags, + mcparticle::PdgCode, mcparticle::StatusCode, mcparticle_v2::Flags, mcparticle::Mother0Id, mcparticle::Mother1Id, mcparticle::Daughter0Id, mcparticle::Daughter1Id, mcparticle::Weight, mcparticle::Px, mcparticle::Py, mcparticle::Pz, mcparticle::E, mcparticle::Vx, mcparticle::Vy, mcparticle::Vz, mcparticle::Vt, mcparticle::PVector, - mcparticle::ProducedByGenerator, - mcparticle::FromBackgroundEvent, - mcparticle::GetGenStatusCode, - mcparticle::GetHepMCStatusCode, - mcparticle::GetProcess, - mcparticle::IsPhysicalPrimary); + mcparticle::ProducedByGenerator, + mcparticle::FromBackgroundEvent, + mcparticle::GetGenStatusCode, + mcparticle::GetHepMCStatusCode, + mcparticle::GetProcess, + mcparticle_v2::ProtectedFlags, + mcparticle_v2::IsPhysicalPrimary); DECLARE_SOA_TABLE_FULL_VERSIONED(StoredMcParticles_001, "McParticles", "AOD", "MCPARTICLE", 1, //! MC particle table, version 001 o2::soa::Index<>, mcparticle::McCollisionId, - mcparticle::PdgCode, mcparticle::StatusCode, mcparticle::Flags, + mcparticle::PdgCode, mcparticle::StatusCode, mcparticle_v2::Flags, mcparticle::MothersIds, mcparticle::DaughtersIdSlice, mcparticle::Weight, mcparticle::Px, mcparticle::Py, mcparticle::Pz, mcparticle::E, mcparticle::Vx, mcparticle::Vy, mcparticle::Vz, mcparticle::Vt, mcparticle::PVector, - mcparticle::ProducedByGenerator, - mcparticle::FromBackgroundEvent, - mcparticle::GetGenStatusCode, - mcparticle::GetHepMCStatusCode, - mcparticle::GetProcess, - mcparticle::IsPhysicalPrimary); + mcparticle::ProducedByGenerator, + mcparticle::FromBackgroundEvent, + mcparticle::GetGenStatusCode, + mcparticle::GetHepMCStatusCode, + mcparticle::GetProcess, + mcparticle_v2::ProtectedFlags, + mcparticle_v2::IsPhysicalPrimary); DECLARE_SOA_EXTENDED_TABLE(McParticles_000, StoredMcParticles_000, "EXMCPARTICLE", 0, //! Basic MC particle properties mcparticle::Phi, diff --git a/Framework/Core/include/Framework/AnalysisHelpers.h b/Framework/Core/include/Framework/AnalysisHelpers.h index c7d567c9eb810..e4d9682b35302 100644 --- a/Framework/Core/include/Framework/AnalysisHelpers.h +++ b/Framework/Core/include/Framework/AnalysisHelpers.h @@ -147,7 +147,7 @@ auto spawner(framework::pack, std::vector>&& if (fullTable->num_rows() == 0) { return makeEmptyTable(name, framework::pack{}); } - return spawnerHelper(fullTable, schema, sizeof...(C), projectors, name, projector); + return spawnerHelper(fullTable.tablePtr, schema, sizeof...(C), projectors, name, projector); } std::string serializeProjectors(std::vector& projectors); @@ -492,12 +492,6 @@ class TableConsumer; /// Helper class actually implementing the cursor which can write to /// a table. The provided template arguments are if type Column and /// therefore refer only to the persisted columns. -template -concept is_producable = soa::has_metadata> || soa::has_metadata>; - -template -concept is_enumerated_iterator = requires(T t) { t.globalIndex(); }; - template struct WritingCursor { public: @@ -579,13 +573,13 @@ struct WritingCursor { decltype(FFL(std::declval())) cursor; private: - static decltype(auto) extract(is_enumerated_iterator auto const& arg) + static decltype(auto) extract(soa::is_enumerated_iterator auto const& arg) { return arg.globalIndex(); } template - requires(!is_enumerated_iterator) + requires(!soa::is_enumerated_iterator) static decltype(auto) extract(A&& arg) { return arg; @@ -642,9 +636,6 @@ template struct Produces : WritingCursor { }; -template -concept is_produces = requires(T t) { typename T::cursor_t; typename T::persistent_table_t; &T::cursor; }; - /// Use this to group together produces. Useful to separate them logically /// or simply to stay within the 100 elements per Task limit. /// Use as: @@ -654,11 +645,9 @@ concept is_produces = requires(T t) { typename T::cursor_t; typename T::persiste /// /// Notice the label MySetOfProduces is just a mnemonic and can be omitted. struct ProducesGroup { + static constexpr void isProducesGroup() {}; }; -template -concept is_produces_group = std::derived_from; - /// Helper template for table transformations template struct TableTransform { @@ -682,12 +671,6 @@ struct TableTransform { /// This helper struct allows you to declare extended tables which should be /// created by the task (as opposed to those pre-defined by data model) -template -concept is_spawnable = soa::has_metadata>> && soa::has_extension>::metadata>; - -template -concept is_dynamically_spawnable = soa::has_metadata>> && soa::has_configurable_extension>::metadata>; - template consteval auto transformBase() { @@ -736,13 +719,6 @@ struct Spawns : decltype(transformBase()) { }(); }; -template -concept is_spawns = requires(T t) { - typename T::metadata; - typename T::expression_pack_t; - requires std::same_as>; -}; - /// This helper struct allows you to declare extended tables with dynamically-supplied /// expressions to be created by the task /// The actual expressions have to be set in init() for the configurable expression @@ -792,15 +768,6 @@ struct Defines : decltype(transformBase()) { template using DefinesDelayed = Defines; -template -concept is_defines = requires(T t) { - typename T::metadata; - typename T::placeholders_pack_t; - requires std::same_as>; - requires std::same_as; - &T::recompile; -}; - /// Policy to control index building /// Exclusive index: each entry in a row has a valid index /// Sparse index: values in a row can be (-1), index table is isomorphic (joinable) to T1 @@ -859,13 +826,6 @@ struct Builds : decltype(transformBase()) { } }; -template -concept is_builds = requires(T t) { - typename T::metadata; - typename T::Key; - requires std::same_as>; -}; - /// a task with rewritten origin, if running together with a task with the default, will /// have a different name and thus its output would be routed separately @@ -877,6 +837,7 @@ concept is_builds = requires(T t) { /// to determine the target file, e.g. analysis result, QA or control histogram, /// etc. template + requires(std::derived_from) struct OutputObj { using obj_t = T; @@ -964,15 +925,6 @@ struct OutputObj { uint32_t mTaskHash; }; -template -concept is_outputobj = requires(T t) { - &T::setHash; - &T::spec; - &T::ref; - requires std::same_as()), typename T::obj_t*>; - requires std::same_as>; -}; - /// This helper allows you to fetch a Sevice from the context or /// by using some singleton. This hopefully will hide the Singleton and /// We will be able to retrieve it in a more thread safe manner later on. @@ -991,12 +943,6 @@ struct Service { } }; -template -concept is_service = requires(T t) { - requires std::same_as; - &T::operator->; -}; - auto getTableFromFilter(soa::is_filtered_table auto const& table, soa::SelectionVector&& selection) { return std::make_unique>>(std::vector{table}, std::forward(selection)); @@ -1004,7 +950,7 @@ auto getTableFromFilter(soa::is_filtered_table auto const& table, soa::Selection auto getTableFromFilter(soa::is_not_filtered_table auto const& table, soa::SelectionVector&& selection) { - return std::make_unique>>(std::vector{table.asArrowTable()}, std::forward(selection)); + return std::make_unique>>(std::vector{table.asArrowTableRef()}, std::forward(selection)); } void initializePartitionCaches(std::set const& hashes, std::shared_ptr const& schema, expressions::Filter const& filter, gandiva::NodePtr& tree, gandiva::FilterPtr& gfilter); @@ -1016,7 +962,7 @@ void initializePartitionCaches(std::set const& hashes, std::shared_ptr /// the real reason is to provide grouped parts for the process functions that request it /// better solution would be to "slice" the selection, as is already done in GroupSlicer /// for the same purpose, instead of reapplying the filtering -template +template struct Partition { using content_t = T; Partition(expressions::Node&& filter_) : filter{std::forward(filter_)} @@ -1036,7 +982,7 @@ struct Partition { void bindTable(T const& table) { - intializeCaches(T::table_t::hashes(), table.asArrowTable()->schema()); + intializeCaches(T::table_t::hashes(), table.asArrowTableRef()->schema()); if (dataframeChanged) { mFiltered = getTableFromFilter(table, soa::selectionToVector(framework::expressions::createSelection(table.asArrowTable(), gfilter))); dataframeChanged = false; @@ -1128,13 +1074,6 @@ struct Partition { return mFiltered->size(); } }; - -template -concept is_partition = requires(T t) { - &T::updatePlaceholders; - requires std::same_as; - requires std::same_as>>; -}; } // namespace o2::framework namespace o2::soa @@ -1147,7 +1086,7 @@ auto Extend(T const& table) static std::array projectors{{std::move(Cs::Projector())...}}; static std::shared_ptr projector = nullptr; static auto schema = std::make_shared(o2::soa::createFieldsFromColumns(framework::pack{})); - return output_t{{o2::framework::spawner(framework::pack{}, {table.asArrowTable()}, "dynamicExtension", projectors.data(), projector, schema), table.asArrowTable()}, 0}; + return output_t{{o2::framework::spawner(framework::pack{}, {table.asArrowTable()}, "dynamicExtension", projectors.data(), projector, schema), table.asArrowTable()}}; } /// Template function to attach dynamic columns on-the-fly (e.g. inside @@ -1156,7 +1095,7 @@ template auto Attach(T const& table) { using output_t = Join, o2::aod::Hash<"JOIN/0"_h>, o2::aod::Hash<"JOIN"_h>, Cs...>>; - return output_t{{table.asArrowTable()}, table.offset()}; + return output_t{{table.asArrowTableRef()}}; } } // namespace o2::soa diff --git a/Framework/Core/include/Framework/AnalysisManagers.h b/Framework/Core/include/Framework/AnalysisManagers.h index bb37fb9016c2f..8b426576ce556 100644 --- a/Framework/Core/include/Framework/AnalysisManagers.h +++ b/Framework/Core/include/Framework/AnalysisManagers.h @@ -319,12 +319,12 @@ bool prepareOutput(ProcessingContext& context, T& spawns) } using D = o2::aod::Hash; - spawns.extension = std::make_shared(o2::framework::spawner(originalTable, + spawns.extension = std::make_shared(o2::framework::spawner(originalTable.tablePtr, o2::aod::label(), spawns.projectors.data(), spawns.projector, spawns.schema)); - spawns.table = std::make_shared(soa::ArrowHelpers::joinTables({spawns.extension->asArrowTable(), originalTable}, std::span{T::spawnable_t::table_t::originalLabels})); + spawns.table = std::make_shared(soa::ArrowHelpers::joinTables({spawns.extension->asArrowTableRef(), originalTable}, std::span{T::spawnable_t::table_t::originalLabels})); return true; } @@ -348,12 +348,12 @@ bool prepareOutput(ProcessingContext& context, T& defines) } using D = o2::aod::Hash; - defines.extension = std::make_shared(o2::framework::spawner(originalTable, + defines.extension = std::make_shared(o2::framework::spawner(originalTable.tablePtr, o2::aod::label(), defines.projectors.data(), defines.projector, defines.schema)); - defines.table = std::make_shared(soa::ArrowHelpers::joinTables({defines.extension->asArrowTable(), originalTable}, std::span{T::spawnable_t::table_t::originalLabels})); + defines.table = std::make_shared(soa::ArrowHelpers::joinTables({defines.extension->asArrowTableRef(), originalTable}, std::span{T::spawnable_t::table_t::originalLabels})); return true; } @@ -380,12 +380,12 @@ bool prepareDelayedOutput(ProcessingContext& context, T& defines) } using D = o2::aod::Hash; - defines.extension = std::make_shared(o2::framework::spawner(originalTable, + defines.extension = std::make_shared(o2::framework::spawner(originalTable.tablePtr, o2::aod::label(), defines.projectors.data(), defines.projector, defines.schema)); - defines.table = std::make_shared(soa::ArrowHelpers::joinTables({defines.extension->asArrowTable(), originalTable}, std::span{T::spawnable_t::table_t::originalLabels})); + defines.table = std::make_shared(soa::ArrowHelpers::joinTables({defines.extension->asArrowTableRef(), originalTable}, std::span{T::spawnable_t::table_t::originalLabels})); return true; } diff --git a/Framework/Core/include/Framework/AnalysisTask.h b/Framework/Core/include/Framework/AnalysisTask.h index 3170236e18f09..aa86525df1721 100644 --- a/Framework/Core/include/Framework/AnalysisTask.h +++ b/Framework/Core/include/Framework/AnalysisTask.h @@ -26,6 +26,8 @@ #include "Framework/TypeIdHelpers.h" #include "Framework/ArrowTableSlicingCache.h" #include "Framework/AnalysisDataModel.h" +#include "Framework/DanglingEdgesContext.h" +#include #include #include @@ -226,7 +228,7 @@ struct AnalysisDataProcessorBuilder { template static auto extractTablesFromRecord(InputRecord& record, R matchers) { - std::vector> tables; + std::vector tables; std::ranges::transform(matchers, std::back_inserter(tables), [&record](auto const& m) { return record.get(m.second)->asArrowTable(); }); @@ -248,8 +250,8 @@ struct AnalysisDataProcessorBuilder { template static auto extractFilteredFromRecord(InputRecord& record, R matchers, ExpressionInfo& info) { - std::shared_ptr table = soa::ArrowHelpers::joinTables(extractTablesFromRecord(record, matchers)); - expressions::updateFilterInfo(info, table); + auto table = soa::ArrowHelpers::joinTables(extractTablesFromRecord(record, matchers)); + expressions::updateFilterInfo(info, table.tablePtr); if constexpr (!o2::soa::is_smallgroups>) { if (info.selection == nullptr) { soa::missingFilterDeclaration(info.processHash, info.argumentIndex); @@ -302,12 +304,20 @@ struct AnalysisDataProcessorBuilder { } template +#if (FAIRMQ_VERSION_DEC >= 111000) + static void invokeProcess(Task& task, InputRecord& inputs, R matchers, PointerReconstructor const& pointerReconstructor, void (Task::*processingFunction)(Grouping, Associated...), std::vector& infos, ArrowTableSlicingCache& slices, header::DataOrigin newOrigin = header::DataOrigin{"AOD"}) +#else static void invokeProcess(Task& task, InputRecord& inputs, R matchers, void (Task::*processingFunction)(Grouping, Associated...), std::vector& infos, ArrowTableSlicingCache& slices, header::DataOrigin newOrigin = header::DataOrigin{"AOD"}) +#endif { using G = std::decay_t; auto groupingTable = AnalysisDataProcessorBuilder::bindGroupingTable(inputs, matchers, processingFunction, infos); - - constexpr const int numElements = nested_brace_constructible_size>() / 10; +#if (FAIRMQ_VERSION_DEC >= 111000) + if constexpr (!is_enumeration) { + groupingTable.setPointerReconstructor(pointerReconstructor); + } +#endif + constexpr const int numElements = homogeneous_apply_refs_size>(); // set filtered tables for partitions with grouping homogeneous_apply_refs_sized([&groupingTable](auto& element) { @@ -347,6 +357,12 @@ struct AnalysisDataProcessorBuilder { ...); }, associatedTables); +#if (FAIRMQ_VERSION_DEC >= 111000) + std::apply([&pointerReconstructor](auto&... table) { + (table.setPointerReconstructor(pointerReconstructor), ...); + }, + associatedTables); +#endif auto binder = [&task, &groupingTable, &associatedTables](auto& x) mutable { x.bindExternalIndices(&groupingTable, &std::get>(associatedTables)...); @@ -377,6 +393,12 @@ struct AnalysisDataProcessorBuilder { auto slicer = GroupSlicer(groupingTable, associatedTables, slices, newOrigin); for (auto& slice : slicer) { auto associatedSlices = slice.associatedTables(); +#if (FAIRMQ_VERSION_DEC >= 111000) + std::apply([&pointerReconstructor](auto&... table) { + (table.setPointerReconstructor(pointerReconstructor), ...); + }, + associatedSlices); +#endif overwriteInternalIndices(associatedSlices, associatedTables); std::apply( [&binder](auto&... x) mutable { @@ -524,7 +546,7 @@ DataProcessorSpec adaptAnalysisTask(ConfigContext const& ctx, Args&&... args) newOrigin.runtimeInit(newOriginStr.c_str(), std::min(newOriginStr.size(), 4UL)); } - constexpr const int numElements = nested_brace_constructible_size>() / 10; + constexpr const int numElements = homogeneous_apply_refs_size>(); /// make sure options and configurables are set before expression infos are created homogeneous_apply_refs_sized([&options](auto& element) { return analysis_task_parsers::appendOption(options, element); }, *task.get()); @@ -580,118 +602,144 @@ DataProcessorSpec adaptAnalysisTask(ConfigContext const& ctx, Args&&... args) // replace origins in Preslice declarations homogeneous_apply_refs_sized([&newOrigin](auto& element) { return analysis_task_parsers::replaceOrigin(element, newOrigin); }, *task.get()); - auto algo = AlgorithmSpec::InitCallback{[task = task, expressionInfos, inputInfos, newOrigin, newOriginStr](InitContext& ic) mutable { - Cache bindingsKeys; - Cache bindingsKeysUnsorted; - // add preslice declarations to slicing cache definition - homogeneous_apply_refs_sized([&bindingsKeys, &bindingsKeysUnsorted](auto& element) { return analysis_task_parsers::registerCache(element, bindingsKeys, bindingsKeysUnsorted); }, *task.get()); - - homogeneous_apply_refs_sized([&ic](auto&& element) { return analysis_task_parsers::prepareOption(ic, element); }, *task.get()); - homogeneous_apply_refs_sized([&ic](auto&& element) { return analysis_task_parsers::prepareService(ic, element); }, *task.get()); - - auto& callbacks = ic.services().get(); - auto eoscb = [task](EndOfStreamContext& eosContext) { - homogeneous_apply_refs_sized([&eosContext](auto& element) { + auto algo = AlgorithmSpec::InitCallback + { + [task = task, expressionInfos, inputInfos, newOrigin, newOriginStr](InitContext& ic) mutable { + Cache bindingsKeys; + Cache bindingsKeysUnsorted; + // add preslice declarations to slicing cache definition + homogeneous_apply_refs_sized([&bindingsKeys, &bindingsKeysUnsorted](auto& element) { return analysis_task_parsers::registerCache(element, bindingsKeys, bindingsKeysUnsorted); }, *task.get()); + + homogeneous_apply_refs_sized([&ic](auto&& element) { return analysis_task_parsers::prepareOption(ic, element); }, *task.get()); + homogeneous_apply_refs_sized([&ic](auto&& element) { return analysis_task_parsers::prepareService(ic, element); }, *task.get()); + + auto& callbacks = ic.services().get(); + auto eoscb = [task](EndOfStreamContext& eosContext) { + homogeneous_apply_refs_sized([&eosContext](auto& element) { analysis_task_parsers::postRunService(eosContext, element); analysis_task_parsers::postRunOutput(eosContext, element); return true; }, - *task.get()); - eosContext.services().get().readyToQuit(QuitRequest::Me); - }; - - callbacks.set(eoscb); - - /// call the task's init() function first as it may manipulate the task's elements - if constexpr (requires { task->init(ic); }) { - task->init(ic); - } - - /// update configurables in filters and partitions - homogeneous_apply_refs_sized( - [&ic](auto& element) -> bool { return analysis_task_parsers::updatePlaceholders(ic, element); }, - *task.get()); - /// create expression trees for filters gandiva trees matched to schemas and store the pointers into expressionInfos - homogeneous_apply_refs_sized([&expressionInfos](auto& element) { - return analysis_task_parsers::createExpressionTrees(expressionInfos, element); - }, - *task.get()); + *task.get()); + eosContext.services().get().readyToQuit(QuitRequest::Me); + }; - /// parse process functions to enable requested grouping caches - note that at this state process configurables have their final values - if constexpr (requires { &T::process; }) { - AnalysisDataProcessorBuilder::cacheFromArgs(&T::process, true, bindingsKeys, bindingsKeysUnsorted); - } - homogeneous_apply_refs_sized( - [&bindingsKeys, &bindingsKeysUnsorted](auto& x) { - return AnalysisDataProcessorBuilder::requestCacheFromArgs(x, bindingsKeys, bindingsKeysUnsorted); - }, - *task.get()); + callbacks.set(eoscb); - /// replace origin in slicing caches - std::ranges::transform(bindingsKeys, bindingsKeys.begin(), [&newOrigin](Entry& entry) { - if ((entry.matcher.origin == header::DataOrigin{"AOD"}) && (newOrigin != header::DataOrigin{"AOD"})) { - entry.matcher = replaceOrigin(entry.matcher, newOrigin); + /// call the task's init() function first as it may manipulate the task's elements + if constexpr (requires { task->init(ic); }) { + task->init(ic); } - return entry; - }); - std::ranges::transform(bindingsKeysUnsorted, bindingsKeysUnsorted.begin(), [&newOrigin](Entry& entry) { - if ((entry.matcher.origin == header::DataOrigin{"AOD"}) && (newOrigin != header::DataOrigin{"AOD"})) { - entry.matcher = replaceOrigin(entry.matcher, newOrigin); - } - return entry; - }); - ic.services().get().setCaches(std::move(bindingsKeys)); - ic.services().get().setCachesUnsorted(std::move(bindingsKeysUnsorted)); - ic.services().get().setOrigin(newOrigin); - - return [task, expressionInfos, inputInfos, newOrigin](ProcessingContext& pc) mutable { - // load the ccdb object from their cache - homogeneous_apply_refs_sized([&pc](auto& element) { return analysis_task_parsers::newDataframeCondition(pc.inputs(), element); }, *task.get()); - // reset partitions once per dataframe - homogeneous_apply_refs_sized([](auto& element) { return analysis_task_parsers::newDataframePartition(element); }, *task.get()); - // reset selections for the next dataframe - std::ranges::for_each(expressionInfos, [](auto& info) { info.resetSelection = true; }); - // reset pre-slice for the next dataframe - auto& slices = pc.services().get(); - homogeneous_apply_refs_sized([&slices](auto& element) { - return analysis_task_parsers::updateSliceInfo(element, slices); + /// update configurables in filters and partitions + homogeneous_apply_refs_sized( + [&ic](auto& element) -> bool { return analysis_task_parsers::updatePlaceholders(ic, element); }, + *task.get()); + /// create expression trees for filters gandiva trees matched to schemas and store the pointers into expressionInfos + homogeneous_apply_refs_sized([&expressionInfos](auto& element) { + return analysis_task_parsers::createExpressionTrees(expressionInfos, element); }, - *(task.get())); - // initialize local caches - homogeneous_apply_refs_sized([&pc](auto& element) { return analysis_task_parsers::initializeCache(pc, element); }, *(task.get())); - // prepare outputs - homogeneous_apply_refs_sized([&pc](auto& element) { return analysis_task_parsers::prepareOutput(pc, element); }, *task.get()); - // execute run() - if constexpr (requires { task->run(pc); }) { - task->run(pc); - } - // execute process() + *task.get()); + + /// parse process functions to enable requested grouping caches - note that at this state process configurables have their final values if constexpr (requires { &T::process; }) { - auto loc = std::ranges::find_if(inputInfos, [](auto const& info) { return info.hash == o2::framework::TypeIdHelpers::uniqueId(); }); - auto matchers = loc == inputInfos.end() ? std::vector>{} : loc->matchers; - AnalysisDataProcessorBuilder::invokeProcess(*(task.get()), pc.inputs(), matchers, &T::process, expressionInfos, slices, newOrigin); + AnalysisDataProcessorBuilder::cacheFromArgs(&T::process, true, bindingsKeys, bindingsKeysUnsorted); } - // execute optional process() homogeneous_apply_refs_sized( - [&pc, &expressionInfos, &task, &slices, &inputInfos, &newOrigin](auto& x) { - if constexpr (is_process_configurable) { - if (x.value == true) { - auto loc = std::ranges::find_if(inputInfos, [](auto const& info) { return info.hash == o2::framework::TypeIdHelpers::uniqueId(); }); - auto matchers = loc == inputInfos.end() ? std::vector>{} : loc->matchers; - AnalysisDataProcessorBuilder::invokeProcess(*task.get(), pc.inputs(), matchers, x.process, expressionInfos, slices, newOrigin); - return true; - } - return false; - } - return false; + [&bindingsKeys, &bindingsKeysUnsorted](auto& x) { + return AnalysisDataProcessorBuilder::requestCacheFromArgs(x, bindingsKeys, bindingsKeysUnsorted); }, *task.get()); - // prepare delayed outputs - homogeneous_apply_refs_sized([&pc](auto& element) { return analysis_task_parsers::prepareDelayedOutput(pc, element); }, *task.get()); - // finalize outputs - homogeneous_apply_refs_sized([&pc](auto& element) { return analysis_task_parsers::finalizeOutput(pc, element); }, *task.get()); - }; - }}; + + /// replace origin in slicing caches + std::ranges::transform(bindingsKeys, bindingsKeys.begin(), [&newOrigin](Entry& entry) { + if ((entry.matcher.origin == header::DataOrigin{"AOD"}) && (newOrigin != header::DataOrigin{"AOD"})) { + entry.matcher = replaceOrigin(entry.matcher, newOrigin); + } + return entry; + }); + std::ranges::transform(bindingsKeysUnsorted, bindingsKeysUnsorted.begin(), [&newOrigin](Entry& entry) { + if ((entry.matcher.origin == header::DataOrigin{"AOD"}) && (newOrigin != header::DataOrigin{"AOD"})) { + entry.matcher = replaceOrigin(entry.matcher, newOrigin); + } + return entry; + }); + + ic.services().get().setCaches(std::move(bindingsKeys)); + ic.services().get().setCachesUnsorted(std::move(bindingsKeysUnsorted)); + ic.services().get().setOrigin(newOrigin); +#if (FAIRMQ_VERSION_DEC >= 111000) + PointerReconstructor pointerReconstructor(nullptr); + bool hasCCDBTables = !ic.services().get().requestedTIMs.empty(); + + return [task, expressionInfos, inputInfos, newOrigin, hasCCDBTables, pointerReconstructor](ProcessingContext& pc) mutable { + if (hasCCDBTables && (!pointerReconstructor)) { + auto& proxy = pc.services().get(); + auto& spec = pc.services().get().requestedTIMs.front(); + pointerReconstructor = proxy.getShmPointerReconstructor(spec, 0); + } +#else + return [task, expressionInfos, inputInfos, newOrigin](ProcessingContext& pc) mutable { +#endif + // load the ccdb object from their cache + homogeneous_apply_refs_sized([&pc](auto& element) { return analysis_task_parsers::newDataframeCondition(pc.inputs(), element); }, *task.get()); + // reset partitions once per dataframe + homogeneous_apply_refs_sized([](auto& element) { return analysis_task_parsers::newDataframePartition(element); }, *task.get()); + // reset selections for the next dataframe + std::ranges::for_each(expressionInfos, [](auto& info) { info.resetSelection = true; }); + // reset pre-slice for the next dataframe + auto& slices = pc.services().get(); + homogeneous_apply_refs_sized([&slices](auto& element) { + return analysis_task_parsers::updateSliceInfo(element, slices); + }, + *(task.get())); + // initialize local caches + homogeneous_apply_refs_sized([&pc](auto& element) { return analysis_task_parsers::initializeCache(pc, element); }, *(task.get())); + // prepare outputs + homogeneous_apply_refs_sized([&pc](auto& element) { return analysis_task_parsers::prepareOutput(pc, element); }, *task.get()); + // execute run() + if constexpr (requires { task->run(pc); }) { + task->run(pc); + } + // execute process() + if constexpr (requires { &T::process; }) { + auto loc = std::ranges::find_if(inputInfos, [](auto const& info) { return info.hash == o2::framework::TypeIdHelpers::uniqueId(); }); + auto matchers = loc == inputInfos.end() ? std::vector>{} : loc->matchers; +#if (FAIRMQ_VERSION_DEC >= 111000) + AnalysisDataProcessorBuilder::invokeProcess(*(task.get()), pc.inputs(), matchers, pointerReconstructor, &T::process, expressionInfos, slices, newOrigin); +#else + AnalysisDataProcessorBuilder::invokeProcess(*(task.get()), pc.inputs(), matchers, &T::process, expressionInfos, slices, newOrigin); +#endif + } + // execute optional process() + homogeneous_apply_refs_sized( +#if (FAIRMQ_VERSION_DEC >= 111000) + [&pc, &expressionInfos, &task, &slices, &inputInfos, &newOrigin, &pointerReconstructor](auto& x) { +#else + [&pc, &expressionInfos, &task, &slices, &inputInfos, &newOrigin](auto& x) { +#endif + if constexpr (is_process_configurable) { + if (x.value == true) { + auto loc = std::ranges::find_if(inputInfos, [](auto const& info) { return info.hash == o2::framework::TypeIdHelpers::uniqueId(); }); + auto matchers = loc == inputInfos.end() ? std::vector>{} : loc->matchers; +#if (FAIRMQ_VERSION_DEC >= 111000) + AnalysisDataProcessorBuilder::invokeProcess(*task.get(), pc.inputs(), matchers, pointerReconstructor, x.process, expressionInfos, slices, newOrigin); +#else + AnalysisDataProcessorBuilder::invokeProcess(*task.get(), pc.inputs(), matchers, x.process, expressionInfos, slices, newOrigin); +#endif + return true; + } + return false; + } + return false; + }, + *task.get()); + // prepare delayed outputs + homogeneous_apply_refs_sized([&pc](auto& element) { return analysis_task_parsers::prepareDelayedOutput(pc, element); }, *task.get()); + // finalize outputs + homogeneous_apply_refs_sized([&pc](auto& element) { return analysis_task_parsers::finalizeOutput(pc, element); }, *task.get()); + }; + } + }; return { name, diff --git a/Framework/Core/include/Framework/ArrowTypes.h b/Framework/Core/include/Framework/ArrowTypes.h index 2673472a81152..6e0c18c42f873 100644 --- a/Framework/Core/include/Framework/ArrowTypes.h +++ b/Framework/Core/include/Framework/ArrowTypes.h @@ -11,12 +11,55 @@ #ifndef O2_FRAMEWORK_ARROWTYPES_H #define O2_FRAMEWORK_ARROWTYPES_H +#include #include "Framework/Traits.h" #include "arrow/type_fwd.h" #include namespace o2::soa { +struct ArrowRange { + uint64_t offset; + int64_t size; + + bool operator!=(ArrowRange const& other) const + { + return (offset != other.offset) && (size != other.size); + } +}; + +struct ArrowTableRef { + std::shared_ptr tablePtr = nullptr; + ArrowRange range{0, 0}; + + ArrowTableRef() = default; + ArrowTableRef(std::shared_ptr table) + : tablePtr{table}, + range{0, table->num_rows()} + { + } + ArrowTableRef(std::shared_ptr table, ArrowRange range_) + : tablePtr{table}, + range{range_} + { + } + + ArrowTableRef makeEmpty() const + { + return {tablePtr, {0, 0}}; + } + + ArrowTableRef slice(ArrowRange newRange) const + { + return {tablePtr, newRange}; + } + + std::shared_ptr const& operator->() const + { + return tablePtr; + } +}; + template struct arrow_array_for { }; @@ -93,6 +136,11 @@ struct arrow_array_for { using type = arrow::FixedSizeListArray; using value_type = int8_t; }; +template +struct arrow_array_for { + using type = arrow::FixedSizeListArray; + using value_type = int64_t; +}; #define ARROW_VECTOR_FOR(_type_) \ template <> \ diff --git a/Framework/Core/include/Framework/Concepts.h b/Framework/Core/include/Framework/Concepts.h new file mode 100644 index 0000000000000..fea40b25ff1ff --- /dev/null +++ b/Framework/Core/include/Framework/Concepts.h @@ -0,0 +1,313 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef O2_FRAMEWORK_CONCEPTS_H +#define O2_FRAMEWORK_CONCEPTS_H + +#include +#include +#include + +namespace o2::aod +{ +template +struct Hash; +/// hash +/// 1. require aod::Hash +template +concept is_aod_hash = requires(T t) { t.isHash(); }; +/// 2. requires aod::Hash with header::DataOrigin +template +concept is_origin_hash = requires(T t) { t.isOriginHash(); }; + +template +struct MetadataTrait; +} // namespace o2::aod + +namespace o2::soa +{ +/// general +/// require a type to be not void +template +concept not_void = requires { requires !std::same_as; }; + +/// columns +/// 1. require a storage-backed column +template +concept is_persistent_column = requires(C c) { c.isIteratableColumn(); }; + +/// 2. require self-index column +template +concept is_self_index_column = requires(C c) { + typename C::compatible_signature; + // requires aod::is_aod_hash; + typename C::self_index_t; + requires std::same_as; +}; + +/// 3. require bindable index column +template +concept is_index_column = requires(C c) { + typename C::binding_t; + requires not_void; +}; + +/// 4. require a column that can be created from an expression +template +concept is_spawnable_column = std::same_as::spawnable_t, std::true_type>; + +/// 5. require an enumerating column, like soa::Index +template +concept is_indexing_column = requires(C c) { c.isEnumeratingColumn(); }; + +/// 6. require a dynamic column +template +concept is_dynamic_column = requires(C c) { c.isDynamicColumn(); }; + +/// 7. require a marking column +template +concept is_marker_column = requires(C c) { c.isMarkingColumn(); }; + +/// 8. require any supported column +template +concept is_column = is_persistent_column || is_dynamic_column || is_indexing_column || is_marker_column; + +/// 9. require a type that can be bound as a column of type B +template +concept can_bind = requires(T&& t) { + { t.B::mColumnIterator }; +}; + +/// 10. require at least one column in an exploded pack to be an indexing column +template +concept has_index = (is_indexing_column || ...); + +/// pack filtering helpers +template +using is_dynamic_t = std::conditional_t, std::true_type, std::false_type>; + +template +using is_indexing_t = std::conditional_t, std::true_type, std::false_type>; + +/// tables, iterators and metadata +/// 1. require a type with parent_t dependent type +template +concept has_parent_t = not_void; + +/// 2. require a MetadataTrait specialization/descendant +template +concept is_metadata_trait = requires(T t) { t.isMetadataTrait(); }; + +/// 3. require a TableMetadata depcialization/descendant +template +concept is_metadata = requires(T t) { t.isTableMetadata(); }; + +/// 4. require a type with non-void metadata dependent type +template +concept has_metadata = is_metadata::metadata>; + +/// 5. require a type with non-void extension_table_t dependent type +template +concept has_extension = is_metadata && not_void::extension_table_t>; + +/// 6. require a type with non-void configurable_t dependent type, that is same as true_type +template +concept has_configurable_extension = has_extension && requires(T t) { typename std::decay_t::configurable_t; requires std::same_as::configurable_t>; }; + +/// 7. require an soa::Table +template +concept is_table = requires(T t) { t.isSOATable(); }; + +/// 8. require a specialization/descendant of a TableIterator +template +concept is_iterator = requires(T t) { t.isTableIterator(); }; + +/// 9. require a table or iterator +template +concept is_table_or_iterator = is_table || is_iterator; + +/// 10. require soa::IndexTable +template +concept is_index_table = requires(T t) { + t.isIndexTable(); +}; + +/// 11. require a type with a filtered policy +template +concept has_filtered_policy = not_void::policy_t> && requires { std::decay_t::policy_t::isFilteredIndexPolicy(); }; + +/// 12. require a filtered table iterator +template +concept is_filtered_iterator = is_iterator && has_filtered_policy; + +/// 13. require a filtered table +template +concept is_filtered_table = requires(T t) { t.isFilteredBase(); }; + +/// 14. require a filtered table or iterator +template +concept is_filtered = is_filtered_table || is_filtered_iterator; + +/// 15. require not filtered table +template +concept is_not_filtered_table = is_table && !is_filtered_table; + +/// 16. require a join +template +concept is_join = requires(T t) { t.isJoin(); }; + +/// 17. require an enumerated iterator +template +concept is_enumerated_iterator = requires(T t) { t.globalIndex(); }; + +/// misc +/// 1. require a type with originals container +template +concept with_originals = requires { + T::originals.size(); +}; + +/// 2. require a type with sources container +template +concept with_sources = requires { + T::sources.size(); +}; + +/// 3. require a type with sources generator method +template +concept with_sources_generator = requires(T t) { + t.generateSources(); +}; + +/// 4. require a type with ccd_urls container +template +concept with_ccdb_urls = requires(T t) { + t.ccdb_urls.size(); +}; + +/// 5. require a type, whos metadata has base_table_t dependant type +template +concept with_base_table = with_originals && has_metadata>> && requires { + typename aod::MetadataTrait>::metadata::base_table_t; +}; + +template +concept with_base_table_ng = not_void; // redicrection should be done at the check site + +/// 6. require a type with expression_pack_t dependant type +template +concept with_expression_pack = requires { + typename T::expression_pack_t{}; +}; + +/// 7. require a type with index_pack_t dependant type +template +concept with_index_pack = requires { + typename T::index_pack_t{}; +}; + +/// 8. require SmallGroups +template +concept is_smallgroups = requires(T t) { t.isSmallGroups(); }; +} // namespace o2::soa + +namespace o2::framework +{ +/// preslice +/// 1. require a preslice policy +template +concept is_preslice_policy = requires(T t) { t.isPreslicePolicy(); }; + +/// 2. require a preslice container +template +concept is_preslice = requires(T t) { t.isPresliceContainer(); }; + +/// 3. reqiure a preslice group +template +concept is_preslice_group = requires(T t) { t.isPresliceGroup(); }; + +/// 4. require a producable entity +template +concept is_producable = soa::has_metadata>> || soa::has_metadata>>; + +/// 5. require produces declaration +template +concept is_produces = requires(T t) { typename T::cursor_t; typename T::persistent_table_t; &T::cursor; }; + +/// 6. require produces group +template +concept is_produces_group = requires(T t) { t.isProducesGroup(); }; + +/// 7. require spawnable entity +template +concept is_spawnable = soa::has_metadata>> && soa::has_extension>::metadata>; + +/// 8. require dynamically spawnable entity +template +concept is_dynamically_spawnable = soa::has_metadata>> && soa::has_configurable_extension>::metadata>; + +/// 9. require spawns declaration +template +concept is_spawns = requires(T t) { + typename T::metadata; + typename T::expression_pack_t; + t.projector.get(); +}; + +/// 10. require defines declaration +template +concept is_defines = requires(T t) { + typename T::metadata; + typename T::placeholders_pack_t; + t.projector.get(); + requires std::same_as; + t.recompile(); +}; + +/// 11. require builds declaration +template +concept is_builds = requires(T t) { + typename T::metadata; + typename T::Key; + t.map.size(); +}; + +/// 12. require outputobj declaration +template +concept is_outputobj = requires(T t) { + &T::setHash; + &T::spec; + &T::ref; + requires std::same_as()), typename T::obj_t*>; + requires std::same_as; +}; + +/// 13. require service declaration +template +concept is_service = requires(T t) { + requires std::same_as; + &T::operator->; +}; + +/// 14. require partition declaration +template +concept is_partition = requires(T t) { + &T::updatePlaceholders; + t.mFiltered.get(); + &T::operator->; + requires std::same_as; + t.begin(); + t.end(); + t.size(); +}; +} // namespace o2::framework + +#endif // O2_FRAMEWORK_CONCEPTS_H diff --git a/Framework/Core/include/Framework/DataAllocator.h b/Framework/Core/include/Framework/DataAllocator.h index 104e418cbcd19..c3a0d58f0c9c8 100644 --- a/Framework/Core/include/Framework/DataAllocator.h +++ b/Framework/Core/include/Framework/DataAllocator.h @@ -42,6 +42,10 @@ // Do not change this for a full inclusion of fair::mq::Device. #include +#include +#if (FAIRMQ_VERSION_DEC >= 111000) +#include +#endif namespace arrow { @@ -115,11 +119,30 @@ struct LifetimeHolder { // invoke the callback early (e.g. for the Product<> case) void release() { - if (ptr && callback) { - callback(*ptr); - delete ptr; - ptr = nullptr; + if (!ptr) { + return; } + + std::unique_ptr released{ptr}; + ptr = nullptr; + auto releaseCallback = std::move(callback); + if (!releaseCallback) { + return; + } + releaseCallback(*released); + } + + // Delete the owned object without invoking the release callback. This is used + // when a partially filled object must be abandoned. + void discard() + { + if (!ptr) { + return; + } + + callback = nullptr; + delete ptr; + ptr = nullptr; } }; @@ -455,6 +478,11 @@ class DataAllocator void snapshot(const Output& spec, const char* payload, size_t payloadSize, o2::header::SerializationMethod serializationMethod = o2::header::gSerializationMethodNone); + /// create a shallow copy of the @a inputPayload and forward it to the output route of @a spec + /// if the transport types of the input and output routes are different, a real copy is created as fallback + void forwardPayload(const Output& spec, fair::mq::Message& inputPayload, + o2::header::SerializationMethod serializationMethod = o2::header::gSerializationMethodNone); + /// make an object of type T and route to output specified by OutputRef /// The object is owned by the framework, returned reference can be used to fill the object. /// @@ -496,6 +524,8 @@ class DataAllocator struct CacheId { int64_t value; + int64_t handle; + int64_t segment; }; enum struct CacheStrategy : int { @@ -507,7 +537,7 @@ class DataAllocator CacheId adoptContainer(const Output& /*spec*/, ContainerT& /*container*/, CacheStrategy /* cache = false */, o2::header::SerializationMethod /* method = header::gSerializationMethodNone*/) { static_assert(always_static_assert_v, "Container cannot be moved. Please make sure it is backed by a o2::pmr::FairMQMemoryResource"); - return {0}; + return {0, 0, 0}; } /// Adopt a PMR container. Notice that the container must be moveable and @@ -599,12 +629,17 @@ DataAllocator::CacheId DataAllocator::adoptContainer(const Output& spec, Contain payloadMessage->GetSize() // ); - CacheId cacheId{0}; // + CacheId cacheId{0, 0, 0}; // if (cache == CacheStrategy::Always) { // The message will be shallow cloned in the cache. Since the // clone is indistinguishable from the original, we can keep sending // the original. cacheId.value = context.addToCache(payloadMessage); +#if (FAIRMQ_VERSION_DEC >= 111000) + auto meta = dynamic_cast(payloadMessage.get())->GetMeta(); + cacheId.handle = meta.fHandle; + cacheId.segment = meta.fSegmentId; +#endif } context.add(std::move(headerMessage), std::move(payloadMessage), routeIndex); diff --git a/Framework/Core/include/Framework/DataTypes.h b/Framework/Core/include/Framework/DataTypes.h index 3d49d6d3c03d0..98a40e8f561b0 100644 --- a/Framework/Core/include/Framework/DataTypes.h +++ b/Framework/Core/include/Framework/DataTypes.h @@ -13,6 +13,7 @@ #include "CommonConstants/LHCConstants.h" +#include #include #include #include @@ -158,6 +159,22 @@ enum MCParticleFlags : uint8_t { }; } // namespace o2::aod::mcparticle::enums +namespace o2::aod::mcparticle +{ +constexpr float maxRadiusForPhysicalPrimary{5.f}; + +struct Tools { + // trivial helper to remove Physical Primary bit + static uint8_t removeIsPhysicalPrimaryBit(uint8_t input_flags, float vx, float vy) + { + if ((std::hypot(vx, vy) > o2::aod::mcparticle::maxRadiusForPhysicalPrimary) && ((input_flags & o2::aod::mcparticle::enums::PhysicalPrimary) == o2::aod::mcparticle::enums::PhysicalPrimary)) { + input_flags = input_flags & ~enums::PhysicalPrimary; // remove physical primary bit, keep others + } + return input_flags; + } +}; +} // namespace o2::aod::mcparticle + namespace o2::aod::run2 { enum Run2EventSelectionCut { diff --git a/Framework/Core/include/Framework/FairMQDeviceProxy.h b/Framework/Core/include/Framework/FairMQDeviceProxy.h index dbdade465f09c..8ab93d83dc1c2 100644 --- a/Framework/Core/include/Framework/FairMQDeviceProxy.h +++ b/Framework/Core/include/Framework/FairMQDeviceProxy.h @@ -20,6 +20,10 @@ #include "Framework/InputRoute.h" #include "Framework/ForwardRoute.h" #include +#include +#if (FAIRMQ_VERSION_DEC >= 111000) +#include +#endif #include namespace o2::header @@ -29,6 +33,9 @@ struct DataHeader; namespace o2::framework { +#if (FAIRMQ_VERSION_DEC >= 111000) +using PointerReconstructor = std::function; +#endif /// Helper class to hide fair::mq::Device headers in the DataAllocator header. /// This is done because fair::mq::Device brings in a bunch of boost.mpl / /// boost.fusion stuff, slowing down compilation times enourmously. @@ -58,6 +65,10 @@ class FairMQDeviceProxy [[nodiscard]] ChannelIndex getForwardChannelIndexByName(std::string const& channelName) const; /// Retrieve the channel index from a given OutputSpec and the associated timeslice [[nodiscard]] ChannelIndex getOutputChannelIndex(OutputSpec const& spec, size_t timeslice) const; +#if (FAIRMQ_VERSION_DEC >= 111000) + /// Retrieve the pointer-reconstruction function for the shm manager for a given input spec + [[nodiscard]] PointerReconstructor getShmPointerReconstructor(InputSpec const& spec, size_t timeslice); +#endif /// Retrieve the channel index from a given OutputSpec and the associated timeslice void getMatchingForwardChannelIndexes(std::vector& result, header::DataHeader const& header, size_t timeslice) const; /// ChannelIndex from a RouteIndex diff --git a/Framework/Core/include/Framework/GroupSlicer.h b/Framework/Core/include/Framework/GroupSlicer.h index 74e5f16c1703f..f06cd6a0cd916 100644 --- a/Framework/Core/include/Framework/GroupSlicer.h +++ b/Framework/Core/include/Framework/GroupSlicer.h @@ -194,7 +194,7 @@ struct GroupSlicer { } } } - std::decay_t typedTable{{originalTable.asArrowTable()}, std::move(s)}; + std::decay_t typedTable{{originalTable.asArrowTableRef()}, std::move(s)}; typedTable.bindInternalIndicesTo(&originalTable); return typedTable; } @@ -218,16 +218,7 @@ struct GroupSlicer { auto oc = sliceInfos[index].getSliceFor(pos); uint64_t offset = oc.first; auto count = oc.second; - if (count == 0) { - // Empty group: avoid slicing every column only to discard it. Cache one - // empty (0-row) table per associated table and reuse it. This is the - // common case for sparse grouping (e.g. collisions with no candidates). - if (!emptyTables[index]) { - emptyTables[index] = originalTable.asArrowTable()->Slice(0, 0); - } - return std::decay_t{{emptyTables[index]}, soa::SelectionVector{}}; - } - auto groupedElementsTable = originalTable.asArrowTable()->Slice(offset, count); + auto groupedElementsTable = originalTable.asArrowTableRef().slice({offset, count}); // for each grouping element we need to slice the selection vector auto start_iterator = std::lower_bound(starts[index], selections[index]->end(), offset); @@ -239,7 +230,7 @@ struct GroupSlicer { return idx - static_cast(offset); }); - std::decay_t typedTable{{groupedElementsTable}, std::move(slicedSelection), offset}; + std::decay_t typedTable{{groupedElementsTable}, std::move(slicedSelection)}; typedTable.bindInternalIndicesTo(&originalTable); return typedTable; } @@ -281,9 +272,6 @@ struct GroupSlicer { std::span groupSelection; std::array const*, sizeof...(A)> selections; std::array::iterator, sizeof...(A)> starts; - // Cached empty (0-row) table per associated table, lazily built and reused - // for empty groups so we do not slice every column on each empty group. - std::array, sizeof...(A)> emptyTables{}; std::array sliceInfos; std::array sliceInfosUnsorted; diff --git a/Framework/Core/include/Framework/GroupedCombinations.h b/Framework/Core/include/Framework/GroupedCombinations.h index b0a6c9e658a10..d8c6aea44f31d 100644 --- a/Framework/Core/include/Framework/GroupedCombinations.h +++ b/Framework/Core/include/Framework/GroupedCombinations.h @@ -70,15 +70,15 @@ struct GroupedCombinationsGenerator { template GroupedIterator(const GroupingPolicy& groupingPolicy, const G& grouping, const std::tuple& associated, SliceCache* cache_) : GroupingPolicy(groupingPolicy), - mGrouping{std::make_shared(std::vector{grouping.asArrowTable()})}, + mGrouping{std::make_shared(std::vector{grouping.asArrowTableRef()})}, mAssociated{std::make_shared>(std::make_tuple(std::get(pack{})>(associated)...))}, mIndexColumns{getMatchingIndexNode()...}, cache{cache_} { if constexpr (soa::is_filtered_table>) { - mGrouping = std::make_shared(std::vector{grouping.asArrowTable()}, grouping.getSelectedRows()); + mGrouping = std::make_shared(std::vector{grouping.asArrowTableRef()}, grouping.getSelectedRows()); } else { - mGrouping = std::make_shared(std::vector{grouping.asArrowTable()}); + mGrouping = std::make_shared(std::vector{grouping.asArrowTableRef()}); } setMultipleGroupingTables(grouping); if (!this->mIsEnd) { @@ -94,9 +94,9 @@ struct GroupedCombinationsGenerator { void setTables(const G& grouping, const std::tuple& associated) { if constexpr (soa::is_filtered_table>) { - mGrouping = std::make_shared(std::vector{grouping.asArrowTable()}, grouping.getSelectedRows()); + mGrouping = std::make_shared(std::vector{grouping.asArrowTableRef()}, grouping.getSelectedRows()); } else { - mGrouping = std::make_shared(std::vector{grouping.asArrowTable()}); + mGrouping = std::make_shared(std::vector{grouping.asArrowTableRef()}); } mAssociated = std::make_shared>(std::make_tuple(std::get(pack{})>(associated)...)); setMultipleGroupingTables(grouping); diff --git a/Framework/Core/include/Framework/InputRecord.h b/Framework/Core/include/Framework/InputRecord.h index ff5c902ec9e40..dc8d8455bb418 100644 --- a/Framework/Core/include/Framework/InputRecord.h +++ b/Framework/Core/include/Framework/InputRecord.h @@ -213,6 +213,9 @@ class InputRecord /// O(1) access to the part described by @a indices in slot @a pos. [[nodiscard]] DataRef getAtIndices(int pos, DataRefIndices indices) const; + /// Return the payload as fair::mq::Message* for the part described by @a indices in slot @a slotIdx + fair::mq::Message* getPayloadAtIndices(size_t slotIdx, DataRefIndices indices) const; + /// O(1) advance from @a current to the next part's indices in slot @a pos. [[nodiscard]] DataRefIndices nextIndices(int pos, DataRefIndices current) const { @@ -745,6 +748,7 @@ class InputRecord [[nodiscard]] DataRefIndices initialIndices() const { return {0, 1}; } [[nodiscard]] DataRefIndices endIndices() const { return {size_t(-1), size_t(-1)}; } [[nodiscard]] DataRef getAtIndices(DataRefIndices idx) const { return record->getAtIndices((int)slot, idx); } + [[nodiscard]] fair::mq::Message* getPayloadAtIndices(DataRefIndices idx) const { return record->getPayloadAtIndices((int)slot, idx); } [[nodiscard]] DataRefIndices nextIndices(DataRefIndices idx) const { return record->nextIndices((int)slot, idx); } [[nodiscard]] size_t size() const { return record->getNofParts((int)slot); } diff --git a/Framework/Core/include/Framework/InputRecordWalker.h b/Framework/Core/include/Framework/InputRecordWalker.h index 528d5ad0c327c..88403cabc1190 100644 --- a/Framework/Core/include/Framework/InputRecordWalker.h +++ b/Framework/Core/include/Framework/InputRecordWalker.h @@ -115,6 +115,11 @@ class InputRecordWalker return not operator==(rh); } + fair::mq::Message* getPayload() const + { + return mCurrentRange.getPayloadAtIndices(mCurrent.indices()); + } + private: bool next(bool isInitialPart = false) { diff --git a/Framework/Core/include/Framework/InputSpan.h b/Framework/Core/include/Framework/InputSpan.h index d708d2e2f5dde..424ea5ad9e139 100644 --- a/Framework/Core/include/Framework/InputSpan.h +++ b/Framework/Core/include/Framework/InputSpan.h @@ -13,9 +13,11 @@ #include "Framework/DataRef.h" #include +#include extern template class std::function; extern template class std::function; +extern template class std::function; namespace o2::framework { @@ -38,6 +40,7 @@ class InputSpan std::function refCountGetter, std::function indicesGetter, std::function nextIndicesGetter, + std::function payloadGetter, size_t size); /// @a i-th element of the InputSpan (O(partidx) sequential scan via indices protocol) @@ -56,6 +59,12 @@ class InputSpan return mIndicesGetter(slotIdx, indices); } + /// Return the payload as fair::mq::Message* for the part described by @a indices in slot @a slotIdx + [[nodiscard]] fair::mq::Message* getPayloadAtIndices(size_t slotIdx, DataRefIndices indices) const + { + return mPayloadGetter(slotIdx, indices); + } + /// Advance from @a current to the indices of the next part in slot @a slotIdx in O(1). [[nodiscard]] DataRefIndices nextIndices(size_t slotIdx, DataRefIndices current) const { @@ -179,6 +188,12 @@ class InputSpan return mCurrentIndices.headerIdx; } + // return current indices + [[nodiscard]] DataRefIndices indices() const + { + return mCurrentIndices; + } + // return an iterable range over all parts in the current slot // only available for slot-level iterators whose parent has parts(size_t) [[nodiscard]] auto parts() const @@ -201,6 +216,7 @@ class InputSpan [[nodiscard]] DataRefIndices initialIndices() const { return {0, 1}; } [[nodiscard]] DataRefIndices endIndices() const { return {size_t(-1), size_t(-1)}; } [[nodiscard]] DataRef getAtIndices(DataRefIndices idx) const { return span->getAtIndices(slot, idx); } + [[nodiscard]] fair::mq::Message* getPayloadAtIndices(DataRefIndices idx) const { return span->getPayloadAtIndices(slot, idx); } [[nodiscard]] DataRefIndices nextIndices(DataRefIndices idx) const { return span->nextIndices(slot, idx); } [[nodiscard]] size_t size() const { return span->getNofParts(slot); } @@ -230,6 +246,7 @@ class InputSpan std::function mRefCountGetter; std::function mIndicesGetter; std::function mNextIndicesGetter; + std::function mPayloadGetter; size_t mSize; }; diff --git a/Framework/Core/include/Framework/TableBuilder.h b/Framework/Core/include/Framework/TableBuilder.h index 41f6d4ea5dc86..3b2d81bc04b00 100644 --- a/Framework/Core/include/Framework/TableBuilder.h +++ b/Framework/Core/include/Framework/TableBuilder.h @@ -558,6 +558,23 @@ constexpr auto tuple_to_pack(std::tuple&&) /// Helper function to convert a brace-initialisable struct to /// a tuple. +#ifdef DPL_STRUCTURED_BINDING_PACKS +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++26-extensions" +#endif +template +auto constexpr to_tuple(T&& object) noexcept +{ + auto&& [... members] = object; + return std::make_tuple(members...); +} +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#else // DPL_STRUCTURED_BINDING_PACKS + template auto constexpr to_tuple(T&& object) noexcept { @@ -579,6 +596,8 @@ auto constexpr to_tuple(T&& object) noexcept } } +#endif // DPL_STRUCTURED_BINDING_PACKS + template constexpr auto makeHolderTypes() { diff --git a/Framework/Core/src/ASoA.cxx b/Framework/Core/src/ASoA.cxx index f565fa6e9ce47..486783c39d18a 100644 --- a/Framework/Core/src/ASoA.cxx +++ b/Framework/Core/src/ASoA.cxx @@ -69,21 +69,6 @@ SelectionVector sliceSelection(std::span const& mSelectedRows, in return slicedSelection; } -std::shared_ptr ArrowHelpers::joinTables(std::vector>&& tables) -{ - std::vector> fields; - std::vector> columns; - bool notEmpty = (tables[0]->num_rows() != 0); - std::ranges::for_each(tables, [&fields, &columns, notEmpty](auto const& t) { - std::ranges::copy(t->fields(), std::back_inserter(fields)); - if (notEmpty) { - std::ranges::copy(t->columns(), std::back_inserter(columns)); - } - }); - auto schema = std::make_shared(fields); - return arrow::Table::Make(schema, columns); -} - namespace { template @@ -109,53 +94,108 @@ void canNotJoin(std::vector> const& tables, std::s } } } -} // namespace -std::shared_ptr ArrowHelpers::joinTables(std::vector>&& tables, std::span labels) +template +void IncompatibleRanges(std::vector const& tables, std::span labels) +{ + auto loc = std::ranges::adjacent_find(tables, [](auto const& l, auto const& r) { return l.range != r.range; }); + if (loc != std::ranges::cend(tables)) { + auto pos = std::distance(tables.begin(), loc); + auto next = loc + 1; + if (labels.empty()) { + throw o2::framework::runtime_error_f("Incompatible ranges at %d: (%zu, %z) vs. (%zu, %z)", pos, loc->range.offset, loc->range.size, next->range.offset, next->range.size); + } else { + throw o2::framework::runtime_error_f("Incompatible ranges at %d between %s and %s: (%zu, %z) vs. (%zu, %z)", pos, makeString(labels[pos]), makeString(labels[pos + 1]), loc->range.offset, loc->range.size, next->range.offset, next->range.size); + } + } +} + +std::shared_ptr joinTablesImpl(std::ranges::input_range auto tables) +{ + std::vector> fields; + std::vector> columns; + bool notEmpty = (tables.front()->num_rows() != 0); + std::ranges::for_each(tables, [&fields, &columns, notEmpty](auto const& t) { + std::ranges::copy(t->fields(), std::back_inserter(fields)); + if (notEmpty) { + std::ranges::copy(t->columns(), std::back_inserter(columns)); + } + }); + auto schema = std::make_shared(fields); + return arrow::Table::Make(schema, columns); +} + +template +ArrowTableRef joinTablesImpl(std::ranges::input_range auto tables, std::span labels) { if (tables.size() == 1) { - return tables[0]; + return tables.front(); } + IncompatibleRanges(tables, labels); + ArrowRange commonRange{tables.front().range}; + return {joinTablesImpl(tables), commonRange}; +} +} // namespace + +o2::soa::ArrowTableRef ArrowHelpers::joinTables(std::vector>&& tables) +{ + std::vector refs; + std::ranges::transform(tables, std::back_inserter(refs), [](auto const& table) { return ArrowTableRef{table}; }); + return joinTablesImpl(refs, std::span()); +} + +o2::soa::ArrowTableRef ArrowHelpers::joinTables(std::vector&& tables) +{ + return joinTablesImpl(tables, std::span()); +} + +o2::soa::ArrowTableRef ArrowHelpers::joinTables(std::vector&& tables, std::span labels) +{ + return joinTablesImpl(tables, labels); +} + +o2::soa::ArrowTableRef ArrowHelpers::joinTables(std::vector&& tables, std::span labels) +{ + return joinTablesImpl(tables, labels); +} + +o2::soa::ArrowTableRef ArrowHelpers::joinTables(std::vector>&& tables, std::span labels) +{ canNotJoin(tables, labels); - return joinTables(std::forward>>(tables)); + return o2::soa::ArrowTableRef{joinTablesImpl(tables)}; } -std::shared_ptr ArrowHelpers::joinTables(std::vector>&& tables, std::span labels) +o2::soa::ArrowTableRef ArrowHelpers::joinTables(std::vector>&& tables, std::span labels) { - if (tables.size() == 1) { - return tables[0]; - } canNotJoin(tables, labels); - return joinTables(std::forward>>(tables)); + return o2::soa::ArrowTableRef{joinTablesImpl(tables)}; } -std::shared_ptr ArrowHelpers::concatTables(std::vector>&& tables) +o2::soa::ArrowTableRef ArrowHelpers::concatTables(std::vector&& tables) { if (tables.size() == 1) { - return tables[0]; + return tables.front(); } std::vector> columns; - std::vector> resultFields = tables[0]->schema()->fields(); + std::vector> resultFields = tables.front()->schema()->fields(); auto compareFields = [](std::shared_ptr const& f1, std::shared_ptr const& f2) { // Let's do this with stable sorting. return (!f1->Equals(f2)) && (f1->name() < f2->name()); }; - for (size_t i = 1; i < tables.size(); ++i) { - auto& fields = tables[i]->schema()->fields(); - std::vector> intersection; - std::set_intersection(resultFields.begin(), resultFields.end(), - fields.begin(), fields.end(), - std::back_inserter(intersection), compareFields); + for (auto i = 1; i < tables.size(); ++i) { + auto const& fields = tables[i]->fields(); + std::vector> intersection; + std::ranges::set_intersection(resultFields, fields, std::back_inserter(intersection), compareFields); resultFields.swap(intersection); } - for (auto& field : resultFields) { + for (auto const& field : resultFields) { arrow::ArrayVector chunks; - for (auto& table : tables) { + for (auto const& table : tables) { auto ci = table->schema()->GetFieldIndex(field->name()); if (ci == -1) { - throw std::runtime_error("Unable to find field " + field->name()); + throw framework::runtime_error_f("Unable to find field {}", field->name().c_str()); } auto column = table->column(ci); auto otherChunks = column->chunks(); @@ -164,15 +204,7 @@ std::shared_ptr ArrowHelpers::concatTables(std::vector(chunks)); } - return arrow::Table::Make(std::make_shared(resultFields), columns); -} - -// ASCII-only lowercase. Column labels are plain identifiers, so we deliberately -// avoid the locale-aware std::tolower: it goes through the C locale facet on -// every character and dominated getIndexFromLabel in profiles. -static constexpr char asciiToLower(char c) -{ - return (c >= 'A' && c <= 'Z') ? static_cast(c + 32) : c; + return {arrow::Table::Make(std::make_shared(resultFields), columns)}; } arrow::ChunkedArray* getIndexFromLabel(arrow::Table* table, std::string_view label) @@ -314,19 +346,10 @@ void PreslicePolicyGeneral::updateSliceInfo(SliceInfoUnsortedPtr&& si) sliceInfo = si; } -std::shared_ptr PreslicePolicySorted::getSliceFor(int value, std::shared_ptr const& input, uint64_t& offset) const +o2::soa::ArrowTableRef PreslicePolicySorted::getSliceFor(int value, o2::soa::ArrowTableRef const& input) const { auto [offset_, count] = this->sliceInfo.getSliceFor(value); - offset = static_cast(offset_); - if (count == 0) { - // Empty group: avoid slicing every column only to discard it. Cache one - // empty (0-row) table per input table and reuse it (see GroupSlicer). - if (emptySlice.first != input.get()) { - emptySlice = {input.get(), input->Slice(0, 0)}; - } - return emptySlice.second; - } - return input->Slice(offset_, count); + return input.slice({static_cast(offset_), count}); } std::span PreslicePolicyGeneral::getSliceFor(int value) const diff --git a/Framework/Core/src/AnalysisHelpers.cxx b/Framework/Core/src/AnalysisHelpers.cxx index 5e46ed86860e8..62229765ddbf8 100644 --- a/Framework/Core/src/AnalysisHelpers.cxx +++ b/Framework/Core/src/AnalysisHelpers.cxx @@ -207,7 +207,7 @@ std::shared_ptr Spawner::materialize(ProcessingContext& pc) const return arrow::Table::MakeEmpty(schema).ValueOrDie(); } - return spawnerHelper(fullTable, schema, binding.c_str(), schema->num_fields(), projector); + return spawnerHelper(fullTable.tablePtr, schema, binding.c_str(), schema->num_fields(), projector); } std::shared_ptr Builder::materialize(ProcessingContext& pc) diff --git a/Framework/Core/src/DataAllocator.cxx b/Framework/Core/src/DataAllocator.cxx index 0802bb8300ae7..92f7a7f8a6e8f 100644 --- a/Framework/Core/src/DataAllocator.cxx +++ b/Framework/Core/src/DataAllocator.cxx @@ -342,6 +342,26 @@ void DataAllocator::snapshot(const Output& spec, const char* payload, size_t pay addPartToContext(routeIndex, std::move(payloadMessage), spec, serializationMethod); } +void DataAllocator::forwardPayload(const Output& spec, fair::mq::Message& inputPayload, + o2::header::SerializationMethod serializationMethod) +{ + auto& proxy = mRegistry.get(); + auto& timingInfo = mRegistry.get(); + + RouteIndex routeIndex = matchDataHeader(spec, timingInfo.timeslice); + auto* transport = proxy.getOutputTransport(routeIndex); + + if (inputPayload.GetTransport() == transport) { + auto payloadMessage = transport->CreateMessage(); + payloadMessage->Copy(inputPayload); + addPartToContext(routeIndex, std::move(payloadMessage), spec, serializationMethod); + } else { + auto payloadMessage = transport->CreateMessage(inputPayload.GetSize(), fair::mq::Alignment{64}); + memcpy(payloadMessage->GetData(), inputPayload.GetData(), inputPayload.GetSize()); + addPartToContext(routeIndex, std::move(payloadMessage), spec, serializationMethod); + } +} + Output DataAllocator::getOutputByBind(OutputRef&& ref) { if (ref.label.empty()) { diff --git a/Framework/Core/src/DataProcessingDevice.cxx b/Framework/Core/src/DataProcessingDevice.cxx index 4121d333f6b56..ab9a66f4e45d3 100644 --- a/Framework/Core/src/DataProcessingDevice.cxx +++ b/Framework/Core/src/DataProcessingDevice.cxx @@ -2203,7 +2203,14 @@ bool DataProcessingDevice::tryDispatchComputation(ServiceRegistryRef ref, std::v auto next = currentSetOfInputs[i] | get_next_pair{current}; return next.headerIdx < currentSetOfInputs[i].size() ? next : DataRefIndices{size_t(-1), size_t(-1)}; }; - return InputSpan{nofPartsGetter, refCountGetter, indicesGetter, nextIndicesGetter, currentSetOfInputs.size()}; + auto payloadGetter = [¤tSetOfInputs](size_t i, DataRefIndices current) -> fair::mq::Message* { + auto const& msgs = currentSetOfInputs[i]; + if (msgs.size() <= current.payloadIdx || !msgs[current.payloadIdx]) { + return nullptr; + } + return msgs[current.payloadIdx].get(); + }; + return InputSpan{nofPartsGetter, refCountGetter, indicesGetter, nextIndicesGetter, payloadGetter, currentSetOfInputs.size()}; }; auto markInputsAsDone = [ref](TimesliceSlot slot) -> void { diff --git a/Framework/Core/src/DataRelayer.cxx b/Framework/Core/src/DataRelayer.cxx index 7adf5b5c97fbb..67c382d413783 100644 --- a/Framework/Core/src/DataRelayer.cxx +++ b/Framework/Core/src/DataRelayer.cxx @@ -236,7 +236,14 @@ DataRelayer::ActivityStats DataRelayer::processDanglingInputs(std::vector(partial.size())}; + auto payloadGetter = [&partial](size_t idx, DataRefIndices current) -> fair::mq::Message* { + auto const& msgs = partial[idx]; + if (msgs.size() <= current.payloadIdx || !msgs[current.payloadIdx]) { + return nullptr; + } + return msgs[current.payloadIdx].get(); + }; + InputSpan span{nPartsGetter, refCountGetter, indicesGetter, nextIndicesGetter, payloadGetter, static_cast(partial.size())}; // Setup the input span if (expirator.checker(services, timestamp.value, span) == false) { @@ -818,7 +825,14 @@ void DataRelayer::getReadyToProcess(std::vector& comp auto next = partial[idx] | get_next_pair{current}; return next.headerIdx < partial[idx].size() ? next : DataRefIndices{size_t(-1), size_t(-1)}; }; - InputSpan span{nPartsGetter, refCountGetter, indicesGetter, nextIndicesGetter, static_cast(partial.size())}; + auto payloadGetter = [&partial](size_t idx, DataRefIndices current) -> fair::mq::Message* { + auto const& msgs = partial[idx]; + if (msgs.size() <= current.payloadIdx || !msgs[current.payloadIdx]) { + return nullptr; + } + return msgs[current.payloadIdx].get(); + }; + InputSpan span{nPartsGetter, refCountGetter, indicesGetter, nextIndicesGetter, payloadGetter, static_cast(partial.size())}; CompletionPolicy::CompletionOp action = mCompletionPolicy.callbackFull(span, mInputs, mContext); auto& variables = mTimesliceIndex.getVariablesForSlot(slot); diff --git a/Framework/Core/src/DeviceSpecHelpers.cxx b/Framework/Core/src/DeviceSpecHelpers.cxx index 64d0f58938941..4c19e7a6ff17b 100644 --- a/Framework/Core/src/DeviceSpecHelpers.cxx +++ b/Framework/Core/src/DeviceSpecHelpers.cxx @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include #include #include "Framework/ChannelConfigurationPolicy.h" @@ -1596,11 +1598,30 @@ void DeviceSpecHelpers::prepareArguments(bool defaultQuiet, bool defaultStopped, } }; + // Fast path for an exact, unambiguously declared long name. An option can + // carry more than one long name, so index all of them. A name declared twice + // is mapped to nullptr, so that it falls back to find_nothrow() below and is + // reported as ambiguous, as it would be without this lookup table. Wildcard + // and short-only names simply miss and fall back as well. + std::unordered_map odescByName; + odescByName.reserve(odesc.options().size()); + for (auto const& optDesc : odesc.options()) { + auto [names, count] = optDesc->long_names(); + for (size_t ni = 0; ni < count; ++ni) { + auto [it, inserted] = odescByName.try_emplace(names[ni], optDesc.get()); + if (!inserted) { + it->second = nullptr; + } + } + } for (const auto& varit : varmap) { // find the option belonging to key, add if the option has been parsed // and is not defaulted - const auto* description = odesc.find_nothrow(varit.first, false); - if (description == nullptr || varmap.count(varit.first) == 0) { + auto descIt = odescByName.find(varit.first); + const auto* description = (descIt != odescByName.end() && descIt->second != nullptr) + ? descIt->second + : odesc.find_nothrow(varit.first, false); + if (description == nullptr) { continue; } diff --git a/Framework/Core/src/FairMQDeviceProxy.cxx b/Framework/Core/src/FairMQDeviceProxy.cxx index e121084b866a2..b1cd9c9352829 100644 --- a/Framework/Core/src/FairMQDeviceProxy.cxx +++ b/Framework/Core/src/FairMQDeviceProxy.cxx @@ -364,4 +364,25 @@ void FairMQDeviceProxy::bind(std::vector const& outputs, std::vecto } mStateChangeCallback = newStatePending; } + +#if (FAIRMQ_VERSION_DEC >= 111000) +PointerReconstructor FairMQDeviceProxy::getShmPointerReconstructor(InputSpec const& spec, size_t timeslice) +{ + assert(mInputRoutes.size() == mInputs.size()); + ChannelIndex c{-1}; + for (size_t ri = 0; ri < mInputs.size(); ++ri) { + auto& route = mInputs[ri]; + + LOG(debug) << "matching: " << DataSpecUtils::describe(spec) << " to route " << DataSpecUtils::describe(route.matcher); + if ((spec == route.matcher) && (timeslice == route.timeslice)) { + c = mInputRoutes[ri].channel; + break; + } + } + if (c.value != ChannelIndex::INVALID) { + return {[transport = getInputChannel(c)->Transport()](fair::mq::shmem::MetaHeader&& meta) { return reinterpret_cast(fair::mq::shmem::GetDataAddressFromHandle(*transport, meta)); }}; + } + return {}; +} +#endif } // namespace o2::framework diff --git a/Framework/Core/src/InputRecord.cxx b/Framework/Core/src/InputRecord.cxx index 7bc9907b13ba4..514f4a33b337a 100644 --- a/Framework/Core/src/InputRecord.cxx +++ b/Framework/Core/src/InputRecord.cxx @@ -149,6 +149,11 @@ DataRef InputRecord::getAtIndices(int pos, DataRefIndices indices) const return ref; } +fair::mq::Message* InputRecord::getPayloadAtIndices(size_t slotIdx, DataRefIndices indices) const +{ + return mSpan.getPayloadAtIndices(slotIdx, indices); +} + size_t InputRecord::size() const { return mSpan.size(); diff --git a/Framework/Core/src/InputSpan.cxx b/Framework/Core/src/InputSpan.cxx index ccea2d1dd66ed..e6a81d3088d72 100644 --- a/Framework/Core/src/InputSpan.cxx +++ b/Framework/Core/src/InputSpan.cxx @@ -13,6 +13,7 @@ template class std::function; template class std::function; +template class std::function; namespace o2::framework { @@ -20,8 +21,9 @@ InputSpan::InputSpan(std::function nofPartsGetter, std::function refCountGetter, std::function indicesGetter, std::function nextIndicesGetter, + std::function payloadGetter, size_t size) - : mNofPartsGetter{nofPartsGetter}, mRefCountGetter(refCountGetter), mIndicesGetter{std::move(indicesGetter)}, mNextIndicesGetter{std::move(nextIndicesGetter)}, mSize{size} + : mNofPartsGetter{nofPartsGetter}, mRefCountGetter(refCountGetter), mIndicesGetter{std::move(indicesGetter)}, mNextIndicesGetter{std::move(nextIndicesGetter)}, mPayloadGetter{std::move(payloadGetter)}, mSize{size} { } diff --git a/DataFormats/Detectors/Upgrades/ALICE3/TRK/src/DataFormatsTRKLinkDef.h b/Framework/Core/src/StepTHnLinkDef.h similarity index 64% rename from DataFormats/Detectors/Upgrades/ALICE3/TRK/src/DataFormatsTRKLinkDef.h rename to Framework/Core/src/StepTHnLinkDef.h index 36528d9dd2c46..550daa56a8b9d 100644 --- a/DataFormats/Detectors/Upgrades/ALICE3/TRK/src/DataFormatsTRKLinkDef.h +++ b/Framework/Core/src/StepTHnLinkDef.h @@ -9,17 +9,12 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#ifdef __CLING__ - #pragma link off all globals; #pragma link off all classes; #pragma link off all functions; -#pragma link C++ class o2::trk::Cluster + ; -#pragma link C++ class std::vector < o2::trk::Cluster> + ; -#pragma link C++ class o2::trk::ROFRecord + ; -#pragma link C++ class std::vector < o2::trk::ROFRecord> + ; -#pragma link C++ class o2::trk::MC2ROFRecord + ; -#pragma link C++ class std::vector < o2::trk::MC2ROFRecord> + ; - -#endif +#pragma link C++ class StepTHn + ; +#pragma link C++ class StepTHnT < TArrayF> + ; +#pragma link C++ class StepTHnT < TArrayD> + ; +#pragma link C++ typedef StepTHnF; +#pragma link C++ typedef StepTHnD; diff --git a/DataFormats/Detectors/Upgrades/ALICE3/TRK/src/Cluster.cxx b/Framework/Core/test/TestClassesLinkDef.h similarity index 56% rename from DataFormats/Detectors/Upgrades/ALICE3/TRK/src/Cluster.cxx rename to Framework/Core/test/TestClassesLinkDef.h index 6c96692ea5a9e..c3cfb448621fb 100644 --- a/DataFormats/Detectors/Upgrades/ALICE3/TRK/src/Cluster.cxx +++ b/Framework/Core/test/TestClassesLinkDef.h @@ -9,20 +9,13 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#include "DataFormatsTRK/Cluster.h" -#include +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; -ClassImp(o2::trk::Cluster); - -namespace o2::trk -{ - -std::string Cluster::asString() const -{ - std::ostringstream stream; - stream << "chip=" << chipID << " row=" << row << " col=" << col << " size=" << size - << " subDet=" << subDetID << " layer=" << layer << " disk=" << disk; - return stream.str(); -} - -} // namespace o2::trk +#pragma link C++ class o2::test::TriviallyCopyable + ; +#pragma link C++ class o2::test::Base + ; +#pragma link C++ class o2::test::Polymorphic + ; +#pragma link C++ class o2::test::SimplePODClass + ; +#pragma link C++ class std::vector < o2::test::TriviallyCopyable> + ; +#pragma link C++ class std::vector < o2::test::Polymorphic> + ; diff --git a/Framework/Core/test/benchmark_DataRelayer.cxx b/Framework/Core/test/benchmark_DataRelayer.cxx index e7df8fbb2fe9b..ca47b63193c1e 100644 --- a/Framework/Core/test/benchmark_DataRelayer.cxx +++ b/Framework/Core/test/benchmark_DataRelayer.cxx @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -140,7 +141,8 @@ static void BM_RelaySingleSlot(benchmark::State& state) auto result = relayer.consumeAllInputsForTimeslice(ready[0].slot); assert(result.size() == 1); assert((result.at(0) | count_parts{}) == 1); - inflightMessages = std::move(result[0]); + inflightMessages.assign(std::make_move_iterator(result[0].begin()), + std::make_move_iterator(result[0].end())); } } @@ -196,7 +198,8 @@ static void BM_RelayMultipleSlots(benchmark::State& state) auto result = relayer.consumeAllInputsForTimeslice(ready[0].slot); assert(result.size() == 1); assert((result.at(0) | count_parts{}) == 1); - inflightMessages = std::move(result[0]); + inflightMessages.assign(std::make_move_iterator(result[0].begin()), + std::make_move_iterator(result[0].end())); } } @@ -271,9 +274,11 @@ static void BM_RelayMultipleRoutes(benchmark::State& state) assert(result.size() == 2); assert((result.at(0) | count_parts{}) == 1); assert((result.at(1) | count_parts{}) == 1); - inflightMessages = std::move(result[0]); - inflightMessages.emplace_back(std::move(result[1][0])); - inflightMessages.emplace_back(std::move(result[1][1])); + inflightMessages.assign(std::make_move_iterator(result[0].begin()), + std::make_move_iterator(result[0].end())); + inflightMessages.insert(inflightMessages.end(), + std::make_move_iterator(result[1].begin()), + std::make_move_iterator(result[1].end())); } } @@ -333,7 +338,9 @@ static void BM_RelaySplitParts(benchmark::State& state) relayer.getReadyToProcess(ready); assert(ready.size() == 1); assert(ready[0].op == CompletionPolicy::CompletionOp::Consume); - inflightMessages = std::move(relayer.consumeAllInputsForTimeslice(ready[0].slot)[0]); + auto result = relayer.consumeAllInputsForTimeslice(ready[0].slot); + inflightMessages.assign(std::make_move_iterator(result[0].begin()), + std::make_move_iterator(result[0].end())); } } @@ -387,7 +394,9 @@ static void BM_RelayMultiplePayloads(benchmark::State& state) relayer.getReadyToProcess(ready); assert(ready.size() == 1); assert(ready[0].op == CompletionPolicy::CompletionOp::Consume); - inflightMessages = std::move(relayer.consumeAllInputsForTimeslice(ready[0].slot)[0]); + auto result = relayer.consumeAllInputsForTimeslice(ready[0].slot); + inflightMessages.assign(std::make_move_iterator(result[0].begin()), + std::make_move_iterator(result[0].end())); } } diff --git a/Framework/Core/test/benchmark_InputRecord.cxx b/Framework/Core/test/benchmark_InputRecord.cxx index e3ec00ac815ed..224dafd53d13b 100644 --- a/Framework/Core/test/benchmark_InputRecord.cxx +++ b/Framework/Core/test/benchmark_InputRecord.cxx @@ -52,6 +52,7 @@ static void BM_InputRecordGenericGetters(benchmark::State& state) nullptr, [](size_t, DataRefIndices) { return DataRef{nullptr, nullptr, nullptr}; }, [](size_t, DataRefIndices) -> DataRefIndices { return {size_t(-1), size_t(-1)}; }, + nullptr, 0}; ServiceRegistry registry; InputRecord emptyRecord(schema, span, registry); @@ -92,6 +93,7 @@ static void BM_InputRecordGenericGetters(benchmark::State& state) nullptr, [&inputs](size_t i, DataRefIndices idx) { return DataRef{nullptr, static_cast(inputs[2 * i + idx.headerIdx]), static_cast(inputs[2 * i + idx.payloadIdx])}; }, [](size_t, DataRefIndices) -> DataRefIndices { return {size_t(-1), size_t(-1)}; }, + nullptr, inputs.size() / 2}; InputRecord record{schema, span2, registry}; diff --git a/Framework/Core/test/test_ASoA.cxx b/Framework/Core/test/test_ASoA.cxx index 48cfb277acc5c..587348f8ffa12 100644 --- a/Framework/Core/test/test_ASoA.cxx +++ b/Framework/Core/test/test_ASoA.cxx @@ -106,7 +106,9 @@ TEST_CASE("TestTableIteration") auto i = ColumnIterator(table->column(0).get()); int64_t pos = 0; + uint64_t offset = 0; i.mCurrentPos = &pos; + i.mGlobalOffset = &offset; REQUIRE(*i == 0); pos++; REQUIRE(*i == 0); @@ -286,7 +288,7 @@ TEST_CASE("TestJoinedTables") REQUIRE(Test::contains()); REQUIRE(!Test::contains()); - Test tests{{tableX, tableY}, 0}; + Test tests{{tableX, tableY}}; REQUIRE(tests.contains()); REQUIRE(tests.contains()); @@ -308,7 +310,7 @@ TEST_CASE("TestJoinedTables") REQUIRE(15 == test.x() + test.y() + test.z()); } using TestMoreThanTwo = Join; - TestMoreThanTwo tests4{{tableX, tableY, tableZ}, 0}; + TestMoreThanTwo tests4{{tableX, tableY, tableZ}}; for (auto& test : tests4) { REQUIRE(15 == test.x() + test.y() + test.z()); } @@ -383,7 +385,7 @@ TEST_CASE("TestConcatTables") static_assert(std::same_as, o2::aod::test::Y, o2::aod::test::X, o2::aod::test::Z>>, "Bad nested join"); static_assert(std::same_as, o2::aod::test::X>>, "Bad intersection of columns"); - ConcatTest tests{tableA, tableB}; + ConcatTest tests{{tableA, tableB}}; REQUIRE(16 == tests.size()); for (auto& test : tests) { REQUIRE(test.index() == test.x()); @@ -428,7 +430,7 @@ TEST_CASE("TestConcatTables") gandiva::Selection selection_f = expressions::createSelection(tableA, testf); TestA testA{tableA}; - FilteredTest filtered{{testA.asArrowTable()}, selection_f}; + FilteredTest filtered{{testA.asArrowTableRef()}, selection_f}; REQUIRE(2 == filtered.size()); auto i = 0; @@ -451,7 +453,7 @@ TEST_CASE("TestConcatTables") selectionConcat->SetIndex(2, 10); selectionConcat->SetNumSlots(3); ConcatTest concatTest{tableA, tableB}; - FilteredConcatTest concatTestTable{{concatTest.asArrowTable()}, selectionConcat}; + FilteredConcatTest concatTestTable{{concatTest.asArrowTableRef()}, selectionConcat}; REQUIRE(3 == concatTestTable.size()); i = 0; @@ -480,8 +482,8 @@ TEST_CASE("TestConcatTables") selectionJoin->SetIndex(1, 2); selectionJoin->SetIndex(2, 4); selectionJoin->SetNumSlots(3); - JoinedTest testJoin{{tableA, tableC}, 0}; - FilteredJoinTest filteredJoin{{testJoin.asArrowTable()}, selectionJoin}; + JoinedTest testJoin{{tableA, tableC}}; + FilteredJoinTest filteredJoin{{testJoin.asArrowTableRef()}, selectionJoin}; i = 0; REQUIRE(filteredJoin.begin() != filteredJoin.end()); @@ -598,12 +600,12 @@ TEST_CASE("TestFilteredOperators") TestA testA{tableA}; auto s1 = expressions::createSelection(testA.asArrowTable(), f1); - FilteredTest filtered1{{testA.asArrowTable()}, s1}; + FilteredTest filtered1{{testA.asArrowTableRef()}, s1}; REQUIRE(4 == filtered1.size()); REQUIRE(filtered1.begin() != filtered1.end()); auto s2 = expressions::createSelection(testA.asArrowTable(), f2); - FilteredTest filtered2{{testA.asArrowTable()}, s2}; + FilteredTest filtered2{{testA.asArrowTableRef()}, s2}; REQUIRE(2 == filtered2.size()); REQUIRE(filtered2.begin() != filtered2.end()); @@ -631,7 +633,7 @@ TEST_CASE("TestFilteredOperators") expressions::Filter f3 = o2::aod::test::x < 3; auto s3 = expressions::createSelection(testA.asArrowTable(), f3); - FilteredTest filtered3{{testA.asArrowTable()}, s3}; + FilteredTest filtered3{{testA.asArrowTableRef()}, s3}; REQUIRE(3 == filtered3.size()); REQUIRE(filtered3.begin() != filtered3.end()); @@ -675,7 +677,7 @@ TEST_CASE("TestNestedFiltering") TestA testA{tableA}; auto s1 = expressions::createSelection(testA.asArrowTable(), f1); - FilteredTest filtered{{testA.asArrowTable()}, s1}; + FilteredTest filtered{{testA.asArrowTableRef()}, s1}; REQUIRE(4 == filtered.size()); REQUIRE(filtered.begin() != filtered.end()); @@ -718,7 +720,7 @@ TEST_CASE("TestEmptyTables") o2::aod::Infos i{iempty}; using PI = Join; - PI pi{{pempty, iempty}, 0}; + PI pi{{pempty, iempty}}; REQUIRE(pi.size() == 0); auto spawned = Extend(p); REQUIRE(spawned.size() == 0); @@ -772,7 +774,7 @@ TEST_CASE("TestIndexToFiltered") expressions::Filter flt = o2::aod::test::someBool == true; using Flt = o2::soa::Filtered; auto selection = expressions::createSelection(o.asArrowTable(), flt); - Flt f{{o.asArrowTable()}, selection}; + Flt f{{o.asArrowTableRef()}, selection}; r.bindExternalIndices(&f); auto it = r.begin(); it.moveByIndex(23); @@ -1084,7 +1086,7 @@ TEST_CASE("TestSelfIndexRecursion") } using FilteredPoints = o2::soa::Filtered; - FilteredPoints ffp({t1, t2}, {1, 2, 3}, 0); + FilteredPoints ffp({t1, t2}, SelectionVector{1, 2, 3}); ffp.bindInternalIndicesTo(&ffp); // Filter should not interfere with self-index and the binding should stay the same @@ -1252,6 +1254,63 @@ TEST_CASE("TestSliceByCachedMismatched") } } +TEST_CASE("TestSliceByCachedFiltered") +{ + TableBuilder b; + auto writer = b.cursor(); + for (auto i = 0; i < 20; ++i) { + writer(0, i, i % 3 == 0); + } + auto origins = b.finalize(); + o2::aod::Origints o{origins}; + + TableBuilder w; + auto writer_w = w.cursor(); + auto step = -1; + for (auto i = 0; i < 5 * 20; ++i) { + if (i % 5 == 0) { + ++step; + } + writer_w(0, step); + } + auto refs = w.finalize(); + o2::aod::References r{refs}; + + TableBuilder w2; + auto writer_w2 = w2.cursor(); + step = -1; + for (auto i = 0; i < 5 * 20; ++i) { + if (i % 3 == 0) { + ++step; + } + writer_w2(0, step); + } + auto refs2 = w2.finalize(); + o2::aod::OtherReferences r2{refs2}; + + using J = o2::soa::Join; + J rr{{refs, refs2}}; + + auto rrf = rr.select(o2::aod::test::altOrigintId > 2 && o2::aod::test::altOrigintId < 15); + + auto key = "fIndex" + o2::framework::cutString(o2::soa::getLabelFromType()) + "_alt"; + ArrowTableSlicingCache atscache({{o2::soa::getLabelFromTypeForKey(key), o2::soa::getMatcherFromTypeForKey(key), key}}); + auto s = atscache.updateCacheEntry(0, refs2); + SliceCache cache{&atscache}; + + for (auto& oi : o) { + auto cachedSlice = rrf.sliceByCached(o2::aod::test::altOrigintId, oi.globalIndex(), cache); + if (oi.globalIndex() <= 2 || oi.globalIndex() >= 15) { + CHECK(cachedSlice.size() == 0); + } else { + CHECK(cachedSlice.size() == 3); + } + for (auto& ri : cachedSlice) { + REQUIRE(ri.altOrigintId() == oi.globalIndex()); + } + } +} + TEST_CASE("TestIndexUnboundExceptions") { TableBuilder b; @@ -1420,5 +1479,5 @@ TEST_CASE("TestWritingCursorLastIndexAndReserve") auto table = builder->finalize(); REQUIRE(table->num_rows() == 5); REQUIRE(table->num_columns() == 2); - delete builder; + cursor.release(); } diff --git a/Framework/Core/test/test_ASoAHelpers.cxx b/Framework/Core/test/test_ASoAHelpers.cxx index c4d7f727aa295..701dc0bbced50 100644 --- a/Framework/Core/test/test_ASoAHelpers.cxx +++ b/Framework/Core/test/test_ASoAHelpers.cxx @@ -72,7 +72,7 @@ TEST_CASE("IteratorTuple") REQUIRE(*(static_cast(std::get<1>(maxOffset2)).getIterator().mCurrentPos) == 8); expressions::Filter filter = test::x > 3; - auto filtered = Filtered{{tests.asArrowTable()}, o2::framework::expressions::createSelection(tests.asArrowTable(), filter)}; + auto filtered = Filtered{{tests.asArrowTableRef()}, o2::framework::expressions::createSelection(tests.asArrowTable(), filter)}; std::tuple, Filtered> filteredTuple = std::make_tuple(filtered, filtered); auto it1 = std::get<0>(filteredTuple).begin(); @@ -164,7 +164,7 @@ TEST_CASE("CombinationsGeneratorConstruction") o2::framework::expressions::Filter filter = test::x > 3; auto s1 = o2::framework::expressions::createSelection(testsA.asArrowTable(), filter); - auto filtered = Filtered{{testsA.asArrowTable()}, s1}; + auto filtered = Filtered{{testsA.asArrowTableRef()}, s1}; CombinationsGenerator, Filtered>>::CombinationsIterator combItFiltered(CombinationsStrictlyUpperIndexPolicy(filtered, filtered)); REQUIRE(!(static_cast(std::get<0>(*(combItFiltered))).getIterator().mCurrentPos == nullptr)); diff --git a/Framework/Core/test/test_AnalysisDataModel.cxx b/Framework/Core/test/test_AnalysisDataModel.cxx index b8b9c161f0e07..ae0914a285110 100644 --- a/Framework/Core/test/test_AnalysisDataModel.cxx +++ b/Framework/Core/test/test_AnalysisDataModel.cxx @@ -49,9 +49,9 @@ TEST_CASE("TestJoinedTablesContains") using Test = o2::soa::Join; - Test tests{{tXY, tZD}, 0}; - REQUIRE(tests.asArrowTable()->num_columns() != 0); - REQUIRE(tests.asArrowTable()->num_columns() == + Test tests{{tXY, tZD}}; + REQUIRE(tests.asArrowTableRef()->num_columns() != 0); + REQUIRE(tests.asArrowTableRef()->num_columns() == tXY->num_columns() + tZD->num_columns()); auto tests2 = join(XY{tXY}, ZD{tZD}); static_assert(std::same_as, diff --git a/Framework/Core/test/test_AnalysisTask.cxx b/Framework/Core/test/test_AnalysisTask.cxx index f5d8c4c43bc38..cb710b9a3871c 100644 --- a/Framework/Core/test/test_AnalysisTask.cxx +++ b/Framework/Core/test/test_AnalysisTask.cxx @@ -314,7 +314,7 @@ TEST_CASE("TestPartitionIteration") expressions::Filter f1 = aod::test::x < 4.0f; auto selection = expressions::createSelection(testA.asArrowTable(), f1); - FilteredTest filtered{{testA.asArrowTable()}, o2::soa::selectionToVector(selection)}; + FilteredTest filtered{{testA.asArrowTableRef()}, o2::soa::selectionToVector(selection)}; PartitionFilteredTest p2 = aod::test::y > 9.0f; p2.bindTable(filtered); diff --git a/Framework/Core/test/test_CompletionPolicy.cxx b/Framework/Core/test/test_CompletionPolicy.cxx index cc16ba95ba8f2..ee7dc7e91df24 100644 --- a/Framework/Core/test/test_CompletionPolicy.cxx +++ b/Framework/Core/test/test_CompletionPolicy.cxx @@ -60,6 +60,7 @@ TEST_CASE("TestCompletionPolicy_callback") nullptr, [&ref](size_t, DataRefIndices) -> DataRef { return ref; }, [](size_t, DataRefIndices) -> DataRefIndices { return {size_t(-1), size_t(-1)}; }, + nullptr, 1}; std::vector specs; ServiceRegistryRef servicesRef{services}; diff --git a/Framework/Core/test/test_Concepts.cxx b/Framework/Core/test/test_Concepts.cxx index 375e537cfaec0..65703082519b6 100644 --- a/Framework/Core/test/test_Concepts.cxx +++ b/Framework/Core/test/test_Concepts.cxx @@ -9,6 +9,7 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. +#include "Framework/Concepts.h" #include #include "Framework/ASoA.h" #include "Framework/AnalysisDataModel.h" @@ -87,6 +88,9 @@ TEST_CASE("IdentificationConcepts") REQUIRE(with_originals); + REQUIRE(o2::soa::is_metadata_trait>>); + REQUIRE(o2::soa::has_metadata>>); + REQUIRE(o2::soa::is_metadata>::metadata>); REQUIRE(with_sources_generator>::metadata>); REQUIRE(with_base_table); @@ -117,7 +121,7 @@ TEST_CASE("IdentificationConcepts") REQUIRE(is_join); - auto tl = []() -> SmallGroups { return {std::vector>{}, SelectionVector{}, 0}; }; + auto tl = []() -> SmallGroups { return {{}, SelectionVector{}}; }; REQUIRE(is_smallgroups); // AnalysisHelpers diff --git a/Framework/Core/test/test_GroupSlicer.cxx b/Framework/Core/test/test_GroupSlicer.cxx index ee6878f23ff80..f282dcbc5c33b 100644 --- a/Framework/Core/test/test_GroupSlicer.cxx +++ b/Framework/Core/test/test_GroupSlicer.cxx @@ -195,8 +195,9 @@ TEST_CASE("GroupSlicerSeveralAssociated") {soa::getLabelFromType(), soa::getMatcherFromTypeForKey(key), key}, {soa::getLabelFromType(), soa::getMatcherFromTypeForKey(key), key}}); auto s = slices.updateCacheEntry(0, {trkTableX}); - s = slices.updateCacheEntry(1, {trkTableY}); - s = slices.updateCacheEntry(2, {trkTableZ}); + s &= slices.updateCacheEntry(1, {trkTableY}); + s &= slices.updateCacheEntry(2, {trkTableZ}); + REQUIRE(s.ok()); o2::framework::GroupSlicer g(e, tt, slices); auto count = 0; @@ -358,7 +359,7 @@ TEST_CASE("GroupSlicerMismatchedFilteredGroups") auto trkTable = builderT.finalize(); using FilteredEvents = soa::Filtered; soa::SelectionVector rows{2, 4, 10, 9, 15}; - FilteredEvents e{{evtTable}, {2, 4, 10, 9, 15}}; + FilteredEvents e{{{evtTable}}, soa::SelectionVector{2, 4, 10, 9, 15}}; aod::TrksX t{trkTable}; REQUIRE(e.size() == 5); REQUIRE(t.size() == 10 * (20 - 4)); @@ -419,7 +420,7 @@ TEST_CASE("GroupSlicerMismatchedUnsortedFilteredGroups") using FilteredEvents = soa::Filtered; soa::SelectionVector rows{2, 4, 10, 9, 15}; - FilteredEvents e{{evtTable}, {2, 4, 10, 9, 15}}; + FilteredEvents e{{evtTable}, soa::SelectionVector{2, 4, 10, 9, 15}}; soa::SmallGroups t{{trkTable}, std::move(sel)}; REQUIRE(e.size() == 5); @@ -631,9 +632,9 @@ TEST_CASE("EmptySliceables") TEST_CASE("ArrowDirectSlicing") { int counts[] = {5, 5, 5, 4, 1}; - int offsets[] = {0, 5, 10, 15, 19, 20}; + int const offsets[] = {0, 5, 10, 15, 19, 20}; int ids[] = {0, 1, 2, 3, 4}; - int sizes[] = {4, 1, 12, 5, 2}; + int const sizes[] = {4, 1, 12, 5, 2}; using BigE = soa::Join; @@ -683,34 +684,25 @@ TEST_CASE("ArrowDirectSlicing") REQUIRE(slices_vec[i]->length() == counts[i]); } - std::vector slices; - std::vector offsts; auto bk = Entry(soa::getLabelFromType(), soa::getMatcherFromTypeForKey("fID"), "fID"); ArrowTableSlicingCache cache({bk}); auto s = cache.updateCacheEntry(0, {evtTable}); + REQUIRE(s.ok()); auto lcache = cache.getCacheFor(bk); for (auto i = 0u; i < 5; ++i) { - auto [offset, count] = lcache.getSliceFor(i); - auto tbl = b_e.asArrowTable()->Slice(offset, count); - auto ca = tbl->GetColumnByName("fArr"); - auto cb = tbl->GetColumnByName("fBoo"); - auto cv = tbl->GetColumnByName("fLst"); - REQUIRE(ca->length() == counts[i]); - REQUIRE(cb->length() == counts[i]); - REQUIRE(cv->length() == counts[i]); - REQUIRE(ca->Equals(slices_array[i])); - REQUIRE(cb->Equals(slices_bool[i])); - REQUIRE(cv->Equals(slices_vec[i])); + auto [loffset, count] = lcache.getSliceFor(i); + auto tbl = b_e.asArrowTableRef().slice({static_cast(loffset), count}); + REQUIRE(tbl.range.size == counts[i]); } int j = 0u; for (auto i = 0u; i < 5; ++i) { - auto [offset, count] = lcache.getSliceFor(i); - auto tbl = BigE{{b_e.asArrowTable()->Slice(offset, count)}, static_cast(offset)}; + auto [loffset, count] = lcache.getSliceFor(i); + auto tbl = BigE{{b_e.asArrowTableRef().slice({static_cast(loffset), count})}}; REQUIRE(tbl.size() == counts[i]); for (auto& row : tbl) { REQUIRE(row.id() == ids[i]); - REQUIRE(row.boo() == (j % 2 == 0)); + CHECK(row.boo() == (j % 2 == 0)); auto rid = row.globalIndex(); auto arr = row.arr(); REQUIRE(arr[0] == 0.1f * (float)rid); @@ -729,7 +721,7 @@ TEST_CASE("ArrowDirectSlicing") TEST_CASE("TestSlicingException") { - int offsets[] = {0, 5, 10, 15, 19, 20}; + int const offsets[] = {0, 5, 10, 15, 19, 20}; int ids[] = {0, 1, 2, 4, 3}; TableBuilder builderE; diff --git a/Framework/Core/test/test_InputRecord.cxx b/Framework/Core/test/test_InputRecord.cxx index 5dff09409325f..6633bd40a30f8 100644 --- a/Framework/Core/test/test_InputRecord.cxx +++ b/Framework/Core/test/test_InputRecord.cxx @@ -51,6 +51,7 @@ TEST_CASE("TestInputRecord") nullptr, [](size_t, DataRefIndices) { return DataRef{nullptr, nullptr, nullptr}; }, [](size_t, DataRefIndices) -> DataRefIndices { return {size_t(-1), size_t(-1)}; }, + nullptr, 0}; ServiceRegistry registry; InputRecord emptyRecord(schema, span, registry); @@ -99,6 +100,7 @@ TEST_CASE("TestInputRecord") nullptr, [&inputs](size_t i, DataRefIndices idx) { return DataRef{nullptr, static_cast(inputs[2 * i + idx.headerIdx]), static_cast(inputs[2 * i + idx.payloadIdx])}; }, [](size_t, DataRefIndices) -> DataRefIndices { return {size_t(-1), size_t(-1)}; }, + nullptr, inputs.size() / 2}; InputRecord record{schema, span2, registry}; diff --git a/Framework/Core/test/test_InputRecordWalker.cxx b/Framework/Core/test/test_InputRecordWalker.cxx index 1fcfea1ba1587..bfbbd3651b5d3 100644 --- a/Framework/Core/test/test_InputRecordWalker.cxx +++ b/Framework/Core/test/test_InputRecordWalker.cxx @@ -40,7 +40,7 @@ struct DataSet { auto payload = static_cast(this->messages[i].second.at(idx.payloadIdx)->data()); return DataRef{nullptr, header, payload}; }, [this](size_t i, DataRefIndices current) -> DataRefIndices { size_t next = current.headerIdx + 2; - return next < this->messages[i].second.size() ? DataRefIndices{next, next + 1} : DataRefIndices{size_t(-1), size_t(-1)}; }, this->messages.size()}, record{schema, span, registry}, values{std::move(v)} + return next < this->messages[i].second.size() ? DataRefIndices{next, next + 1} : DataRefIndices{size_t(-1), size_t(-1)}; }, nullptr, this->messages.size()}, record{schema, span, registry}, values{std::move(v)} { REQUIRE(messages.size() == schema.size()); } diff --git a/Framework/Core/test/test_InputSpan.cxx b/Framework/Core/test/test_InputSpan.cxx index f8d043a2a48ba..9c3ae67e0e063 100644 --- a/Framework/Core/test/test_InputSpan.cxx +++ b/Framework/Core/test/test_InputSpan.cxx @@ -41,7 +41,7 @@ TEST_CASE("TestInputSpan") return next < inputs[i].size() ? DataRefIndices{next, next + 1} : DataRefIndices{size_t(-1), size_t(-1)}; }; - InputSpan span{nPartsGetter, nullptr, indicesGetter, nextIndicesGetter, inputs.size()}; + InputSpan span{nPartsGetter, nullptr, indicesGetter, nextIndicesGetter, nullptr, inputs.size()}; REQUIRE(span.size() == inputs.size()); routeNo = 0; for (; routeNo < span.size(); ++routeNo) { diff --git a/Framework/Core/test/test_Root2ArrowTable.cxx b/Framework/Core/test/test_Root2ArrowTable.cxx index dacb54eb5ecdf..ea97f6bcd8d3e 100644 --- a/Framework/Core/test/test_Root2ArrowTable.cxx +++ b/Framework/Core/test/test_Root2ArrowTable.cxx @@ -31,7 +31,6 @@ #include #include #include -#include #include #include diff --git a/Framework/Core/test/test_TableSpawner.cxx b/Framework/Core/test/test_TableSpawner.cxx index e200adf37ccb4..d5bd4c83068ac 100644 --- a/Framework/Core/test/test_TableSpawner.cxx +++ b/Framework/Core/test/test_TableSpawner.cxx @@ -53,7 +53,7 @@ TEST_CASE("TestTableSpawner") auto expoints_a = o2::soa::Extend(st1); Spawns s; auto extension = ExPointsExtension{o2::framework::spawner>(t1, o2::aod::Hash<"ExPoints"_h>::str, s.projectors.data(), s.projector, s.schema)}; - auto expoints = ExPoints{{t1, extension.asArrowTable()}, 0}; + auto expoints = ExPoints{{t1, extension.asArrowTable()}}; REQUIRE(expoints_a.size() == 9); REQUIRE(extension.size() == 9); @@ -81,7 +81,7 @@ TEST_CASE("TestTableSpawner") excpts.projectors[0] = test::x * test::x + test::y * test::y + test::z * test::z; auto extension_2 = ExcPointsCfgExtension{o2::framework::spawner>({t1}, o2::aod::Hash<"ExcPoints"_h>::str, excpts.projectors.data(), excpts.projector, excpts.schema)}; - auto excpoints = ExcPoints{{t1, extension_2.asArrowTable()}, 0}; + auto excpoints = ExcPoints{{t1, extension_2.asArrowTable()}}; rex = extension.begin(); auto rex_2 = extension_2.begin(); diff --git a/Framework/Foundation/3rdparty/x9/x9.c b/Framework/Foundation/3rdparty/x9/x9.c index 2ca4bb80237b3..28684183cc954 100644 --- a/Framework/Foundation/3rdparty/x9/x9.c +++ b/Framework/Foundation/3rdparty/x9/x9.c @@ -38,6 +38,7 @@ #if defined(__x86_64__) || defined(__i386__) #include /* _mm_pause */ #elif defined(__aarch64__) +#elif defined(__riscv) #else #error Not supported architecture #endif @@ -370,6 +371,8 @@ void x9_read_from_inbox_spin(x9_inbox* const inbox, _mm_pause(); #elif defined(__aarch64__) __asm__ __volatile__ ("yield"); +#elif defined(__riscv) + __asm__ __volatile__ (".4byte 0x0100000F"); /* PAUSE hint (Zihintpause); NOP if unsupported */ #else #error Not supported architecture #endif diff --git a/Framework/Foundation/include/Framework/StructToTuple.h b/Framework/Foundation/include/Framework/StructToTuple.h index 1c7aa62260bd3..e06df1bab984a 100644 --- a/Framework/Foundation/include/Framework/StructToTuple.h +++ b/Framework/Foundation/include/Framework/StructToTuple.h @@ -14,6 +14,14 @@ #include #include +// Structured binding packs (P1061) are C++26, but clang implements them as an +// extension in every language mode and advertises them via the feature test +// macro, so we can use them while still compiling as C++20. +#if defined(__cpp_structured_bindings) && __cpp_structured_bindings >= 202411L +#define DPL_STRUCTURED_BINDING_PACKS 1 +#endif + +#ifndef DPL_STRUCTURED_BINDING_PACKS namespace { template @@ -24,9 +32,11 @@ template std::false_type brace_test(...); } // namespace +#endif namespace o2::framework { +#ifndef DPL_STRUCTURED_BINDING_PACKS struct any_type { template constexpr operator T(); // non explicit @@ -35,19 +45,67 @@ struct any_type { template struct is_braces_constructible : decltype(brace_test(0)) { }; +#endif -#define DPL_REPEAT_0(x) -#define DPL_REPEAT_1(x) x -#define DPL_REPEAT_2(x) x, x -#define DPL_REPEAT_3(x) x, x, x -#define DPL_REPEAT_4(x) x, x, x, x -#define DPL_REPEAT_5(x) x, x, x, x, x -#define DPL_REPEAT_6(x) x, x, x, x, x, x -#define DPL_REPEAT_7(x) x, x, x, x, x, x, x -#define DPL_REPEAT_8(x) x, x, x, x, x, x, x, x -#define DPL_REPEAT_9(x) x, x, x, x, x, x, x, x, x -#define DPL_REPEAT_10(x) x, x, x, x, x, x, x, x, x, x -#define DPL_REPEAT(x, d, u) DPL_REPEAT_##d(DPL_REPEAT_10(x)), DPL_REPEAT_##u(x) +struct UniversalType { + template + operator T() + { + } +}; + +template +consteval auto brace_constructible_size(auto... Members) +{ + if constexpr (requires { T{Members...}; } == false) { + static_assert(sizeof...(Members) != 0, "You need to make sure that you have implicit constructors or that you call the explicit constructor correctly."); + return sizeof...(Members) - 1; + } else { + return brace_constructible_size(Members..., UniversalType{}); + } +} + +template +consteval int nested_brace_constructible_size() +{ + using type = std::decay_t; + constexpr int nesting = B ? 1 : 0; + return brace_constructible_size() - nesting; +} + +/// The size to be passed to homogeneous_apply_refs_sized for T. Structured +/// binding packs do not need to know how many members T has, so we can skip +/// counting them altogether. +template +consteval int homogeneous_apply_refs_size() +{ +#ifdef DPL_STRUCTURED_BINDING_PACKS + return 0; +#else + return nested_brace_constructible_size() / 10; +#endif +} + +#ifdef DPL_STRUCTURED_BINDING_PACKS +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++26-extensions" +#endif +template +constexpr auto homogeneous_apply_refs(L l, T&& object) +{ + auto&& [... members] = object; + if constexpr (sizeof...(members) == 0) { + return std::array(); + } else { + return std::array{l(members)...}; + } +} +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#else // DPL_STRUCTURED_BINDING_PACKS #define DPL_ENUM_0(pre, post) #define DPL_ENUM_1(pre, post) pre##0##post @@ -97,54 +155,6 @@ struct is_braces_constructible : decltype(brace_test(0)) { #define DPL_FENUM(f, pre, post, d, u) DPL_FENUM_##d##0(f, pre, post), DPL_FENUM_##u(f, pre##d, post) -#define DPL_10_As DPL_REPEAT_10(A) -#define DPL_20_As DPL_10_As, DPL_10_As -#define DPL_30_As DPL_20_As, DPL_10_As -#define DPL_40_As DPL_30_As, DPL_10_As -#define DPL_50_As DPL_40_As, DPL_10_As -#define DPL_60_As DPL_50_As, DPL_10_As -#define DPL_70_As DPL_60_As, DPL_10_As -#define DPL_80_As DPL_70_As, DPL_10_As -#define DPL_90_As DPL_80_As, DPL_10_As -#define DPL_100_As DPL_90_As, DPL_10_As - -#define DPL_0_9(pre, po) pre##0##po, pre##1##po, pre##2##po, pre##3##po, pre##4##po, pre##5##po, pre##6##po, pre##7##po, pre##8##po, pre##9##po - -#define BRACE_CONSTRUCTIBLE_ENTRY_LOW(u) \ - constexpr(is_braces_constructible{}) \ - { \ - return u; \ - } -#define BRACE_CONSTRUCTIBLE_ENTRY(d, u) \ - constexpr(is_braces_constructible{}) \ - { \ - return d##u; \ - } - -#define BRACE_CONSTRUCTIBLE_ENTRY_TENS(d) \ - constexpr(is_braces_constructible{}) \ - { \ - return d##0; \ - } - -struct UniversalType { - template - operator T() - { - } -}; - -template -consteval auto brace_constructible_size(auto... Members) -{ - if constexpr (requires { T{Members...}; } == false) { - static_assert(sizeof...(Members) != 0, "You need to make sure that you have implicit constructors or that you call the explicit constructor correctly."); - return sizeof...(Members) - 1; - } else { - return brace_constructible_size(Members..., UniversalType{}); - } -} - #define DPL_HOMOGENEOUS_APPLY_ENTRY_LOW(u) \ constexpr(numElements == u) \ { \ @@ -166,14 +176,6 @@ consteval auto brace_constructible_size(auto... Members) return std::array{DPL_FENUM_##d##0(l, p, )}; \ } -template -consteval int nested_brace_constructible_size() -{ - using type = std::decay_t; - constexpr int nesting = B ? 1 : 0; - return brace_constructible_size() - nesting; -} - template () / 10, typename L> requires(D == 9) constexpr auto homogeneous_apply_refs(L l, T&& object) @@ -373,6 +375,8 @@ constexpr auto homogeneous_apply_refs(L l, T&& object) // clang-format on } +#endif // DPL_STRUCTURED_BINDING_PACKS + template constexpr auto homogeneous_apply_refs_sized(L l, T&& object) { diff --git a/Framework/Foundation/test/test_StructToTuple.cxx b/Framework/Foundation/test/test_StructToTuple.cxx index 59685a5f1d598..4b8a69e837d6a 100644 --- a/Framework/Foundation/test/test_StructToTuple.cxx +++ b/Framework/Foundation/test/test_StructToTuple.cxx @@ -164,3 +164,32 @@ TEST_CASE("TestStructToTuple") REQUIRE(t6.size() == 3); REQUIRE(t6[0] == true); } + +/// Empty base class, mirroring o2::framework::ConfigurableGroup: structs +/// deriving from it must decompose to their own members only, and the empty +/// base must not be counted or bound. Exercised with B=true, as the option +/// group handling in AnalysisManagers.h does. +struct EmptyBase { +}; + +struct DerivedGroup : EmptyBase { + int a = 3; + int b = 30; + int c = 300; +}; + +TEST_CASE("EmptyBaseDestructuring") +{ + DerivedGroup g; + auto t = o2::framework::homogeneous_apply_refs([](auto i) -> bool { return i > 20; }, g); + REQUIRE(t.size() == 3); + REQUIRE(t[0] == false); + REQUIRE(t[1] == true); + REQUIRE(t[2] == true); + + // The binding must reach the derived members, not the base. + o2::framework::homogeneous_apply_refs([](auto& i) { i += 1; return true; }, g); + REQUIRE(g.a == 4); + REQUIRE(g.b == 31); + REQUIRE(g.c == 301); +} diff --git a/Framework/Utils/CMakeLists.txt b/Framework/Utils/CMakeLists.txt index fcbc53ef0e6f0..486c3a42e6b16 100644 --- a/Framework/Utils/CMakeLists.txt +++ b/Framework/Utils/CMakeLists.txt @@ -34,7 +34,7 @@ o2_add_executable(output-proxy o2_add_test(RootTreeWriterWorkflow NO_BOOST_TEST SOURCES test/test_RootTreeWriterWorkflow.cxx - PUBLIC_LINK_LIBRARIES O2::DPLUtils + PUBLIC_LINK_LIBRARIES O2::DPLUtils O2::FrameworkTestSupport COMPONENT_NAME DPLUtils LABELS dplutils COMMAND_LINE_ARGS ${DPL_WORKFLOW_TESTS_EXTRA_OPTIONS} --run) @@ -42,7 +42,7 @@ o2_add_test(RootTreeWriterWorkflow o2_add_test(RootTreeReader NO_BOOST_TEST SOURCES test/test_RootTreeReader.cxx - PUBLIC_LINK_LIBRARIES O2::DPLUtils + PUBLIC_LINK_LIBRARIES O2::DPLUtils O2::FrameworkTestSupport COMPONENT_NAME DPLUtils LABELS dplutils COMMAND_LINE_ARGS ${DPL_WORKFLOW_TESTS_EXTRA_OPTIONS} --run) @@ -53,7 +53,7 @@ add_executable(o2-test-framework-utils test/test_DPLRawParser.cxx test/test_DPLRawPageSequencer.cxx ) -target_link_libraries(o2-test-framework-utils PRIVATE O2::Framework O2::DPLUtils O2::DetectorsRaw) +target_link_libraries(o2-test-framework-utils PRIVATE O2::Framework O2::DPLUtils O2::DetectorsRaw O2::FrameworkTestSupport) target_link_libraries(o2-test-framework-utils PRIVATE O2::Catch2) get_filename_component(outdir ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/../tests ABSOLUTE) diff --git a/Framework/Utils/test/RawPageTestData.h b/Framework/Utils/test/RawPageTestData.h index 29ac4eeba6b5b..e219f8ba84637 100644 --- a/Framework/Utils/test/RawPageTestData.h +++ b/Framework/Utils/test/RawPageTestData.h @@ -53,6 +53,7 @@ struct DataSet { size_t next = current.headerIdx + 2; return next < this->messages[i].size() ? DataRefIndices{next, next + 1} : DataRefIndices{size_t(-1), size_t(-1)}; }, + nullptr, this->messages.size()}, record{schema, span, registry}, values{std::move(v)} diff --git a/Framework/Utils/test/test_RootTreeWriter.cxx b/Framework/Utils/test/test_RootTreeWriter.cxx index e372fb4e1302e..ebacd0e71e5c3 100644 --- a/Framework/Utils/test/test_RootTreeWriter.cxx +++ b/Framework/Utils/test/test_RootTreeWriter.cxx @@ -231,6 +231,7 @@ TEST_CASE("test_RootTreeWriter") return DataRef{nullptr, static_cast(store[2 * i + idx.headerIdx]->GetData()), static_cast(store[2 * i + idx.payloadIdx]->GetData())}; }, [](size_t, DataRefIndices) -> DataRefIndices { return {size_t(-1), size_t(-1)}; }, + nullptr, store.size() / 2}; ServiceRegistry registry; InputRecord inputs{ diff --git a/GPU/GPUTracking/Base/GPUReconstruction.cxx b/GPU/GPUTracking/Base/GPUReconstruction.cxx index 9fd7d16c99f11..eaf3afbdc9cf3 100644 --- a/GPU/GPUTracking/Base/GPUReconstruction.cxx +++ b/GPU/GPUTracking/Base/GPUReconstruction.cxx @@ -66,6 +66,7 @@ struct GPUReconstructionPipelineContext { std::queue pipelineQueue; std::mutex mutex; std::condition_variable cond; + bool workerRunning = false; bool terminate = false; }; } // namespace o2::gpu @@ -275,6 +276,8 @@ int32_t GPUReconstruction::InitPhaseBeforeDevice() if (GetProcessingSettings().deterministicGPUReconstruction) { if (!detMode) { GPUError("WARNING, deterministicGPUReconstruction needs GPUCA_DETERMINISTIC_MODE for being fully deterministic, without only most indeterminism by concurrency is removed, but floating point effects remain!"); + } else { + GPUInfo("GPU Deterministic Reconstruction is enabled"); } if (mProcessingSettings->debugLevel >= 6 && ((mProcessingSettings->debugMask + 1) & mProcessingSettings->debugMask)) { GPUError("WARNING: debugMask %d - debug output might not be deterministic with intermediate steps missing", mProcessingSettings->debugMask); @@ -1092,6 +1095,7 @@ void GPUReconstruction::RunPipelineWorker() { std::unique_lock lk(mPipelineContext->mutex); mPipelineContext->cond.wait(lk, [this] { return this->mPipelineContext->pipelineQueue.size() > 0; }); + mPipelineContext->workerRunning = true; } GPUReconstructionPipelineQueue* q; { @@ -1109,6 +1113,8 @@ void GPUReconstruction::RunPipelineWorker() q->done = true; } q->c.notify_one(); + mPipelineContext->workerRunning = false; + mPipelineContext->cond.notify_one(); } if (GetProcessingSettings().debugLevel >= 3) { GPUInfo("Pipeline worker ended"); @@ -1120,6 +1126,12 @@ void GPUReconstruction::TerminatePipelineWorker() EnqueuePipeline(true); } +void GPUReconstruction::DrainPipeline() +{ + std::unique_lock lk(mPipelineContext->mutex); + mPipelineContext->cond.wait(lk, [this] { return this->mPipelineContext->pipelineQueue.empty() && !this->mPipelineContext->workerRunning; }); +} + int32_t GPUReconstruction::EnqueuePipeline(bool terminate) { ClearAllocatedMemory(true); diff --git a/GPU/GPUTracking/Base/GPUReconstruction.h b/GPU/GPUTracking/Base/GPUReconstruction.h index 4479eb696808e..d85c29371f8fb 100644 --- a/GPU/GPUTracking/Base/GPUReconstruction.h +++ b/GPU/GPUTracking/Base/GPUReconstruction.h @@ -98,6 +98,11 @@ class GPUReconstruction static constexpr GeometryType geometryType = GeometryType::O2; #endif + enum retValValue : uint32_t { ok = 0, + error = 1, + doExit = 2, + nonFatalErrorCode = 3, + abort = 4 }; static DeviceType GetDeviceType(const char* type); enum InOutPointerType : uint32_t { CLUSTER_DATA = 0, SECTOR_OUT_TRACK = 1, @@ -159,6 +164,7 @@ class GPUReconstruction int32_t CheckErrorCodes(bool cpuOnly = false, bool forceShowErrors = false, std::vector>* fillErrors = nullptr); void RunPipelineWorker(); void TerminatePipelineWorker(); + void DrainPipeline(); // Helpers for memory allocation GPUMemoryResource& Res(int16_t num) { return mMemoryResources[num]; } diff --git a/GPU/GPUTracking/Base/GPUReconstructionCPU.cxx b/GPU/GPUTracking/Base/GPUReconstructionCPU.cxx index 9fbe9e1171af3..ac2c2d0a78a78 100644 --- a/GPU/GPUTracking/Base/GPUReconstructionCPU.cxx +++ b/GPU/GPUTracking/Base/GPUReconstructionCPU.cxx @@ -245,7 +245,7 @@ int32_t GPUReconstructionCPU::RunChains() retVal = mChains[i]->RunChain(); } } - if (retVal != 0 && retVal != 2) { + if (retVal != GPUReconstruction::retValValue::ok && retVal != GPUReconstruction::retValValue::doExit) { return retVal; } mTimerTotal.Stop(); diff --git a/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDA.cu b/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDA.cu index 8628abb1c0374..9ac021974b8a1 100644 --- a/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDA.cu +++ b/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDA.cu @@ -194,7 +194,7 @@ int32_t GPUReconstructionCUDA::InitDevice_Runtime() bool noDevice = false; if (bestDevice == -1) { - GPUWarning("No %sCUDA Device available, aborting CUDA Initialisation (Required mem: %ld)", count ? "appropriate " : "", (int64_t)mDeviceMemorySize); + GPUWarning("No %sCUDA Device available, aborting CUDA Initialisation (Required mem: %ld, scanned %d devices)", count ? "appropriate " : "", (int64_t)mDeviceMemorySize, count); #ifndef __HIPCC__ GPUImportant("Requiring Revision %d.%d, Mem: %lu", reqVerMaj, reqVerMin, std::max(mDeviceMemorySize, REQUIRE_MIN_MEMORY)); #endif @@ -228,7 +228,7 @@ int32_t GPUReconstructionCUDA::InitDevice_Runtime() GPUChkErrI(cudaGetDeviceProperties(&deviceProp, mDeviceId)); if (GetProcessingSettings().debugLevel >= 2) { - GPUInfo("Using CUDA Device %s with Properties:", deviceProp.name); + GPUInfo("Using CUDA Device %d: %s with Properties:", bestDevice, deviceProp.name); GPUInfo("\ttotalGlobalMem = %ld", (uint64_t)deviceProp.totalGlobalMem); GPUInfo("\tsharedMemPerBlock = %ld", (uint64_t)deviceProp.sharedMemPerBlock); GPUInfo("\tregsPerBlock = %d", deviceProp.regsPerBlock); @@ -594,7 +594,11 @@ void GPUReconstructionCUDA::PrintKernelOccupancies() int32_t maxBlocks = 0, threads = 0, suggestedBlocks = 0, nRegs = 0, sMem = 0; GPUChkErr(cudaSetDevice(mDeviceId)); for (uint32_t i = 0; i < mInternals->kernelFunctions.size(); i++) { - GPUChkErr(cuOccupancyMaxPotentialBlockSize(&suggestedBlocks, &threads, *mInternals->kernelFunctions[i], 0, 0, 0)); // NOLINT: failure in clang-tidy +#if !defined(__HIPCC__) || (defined(__clang_major__) && __clang_major__ < 23) // CUDA + GPUChkErr(cuOccupancyMaxPotentialBlockSize(&suggestedBlocks, &threads, *mInternals->kernelFunctions[i], 0, 0, 0)); +#else + GPUChkErr(cuOccupancyMaxPotentialBlockSize(&suggestedBlocks, &threads, *mInternals->kernelFunctions[i], 0, 0)); +#endif GPUChkErr(cuOccupancyMaxActiveBlocksPerMultiprocessor(&maxBlocks, *mInternals->kernelFunctions[i], threads, 0)); GPUChkErr(cuFuncGetAttribute(&nRegs, CU_FUNC_ATTRIBUTE_NUM_REGS, *mInternals->kernelFunctions[i])); GPUChkErr(cuFuncGetAttribute(&sMem, CU_FUNC_ATTRIBUTE_SHARED_SIZE_BYTES, *mInternals->kernelFunctions[i])); diff --git a/GPU/GPUTracking/Definitions/GPUSettingsList.h b/GPU/GPUTracking/Definitions/GPUSettingsList.h index cbd8f07752617..842576621c39c 100644 --- a/GPU/GPUTracking/Definitions/GPUSettingsList.h +++ b/GPU/GPUTracking/Definitions/GPUSettingsList.h @@ -119,7 +119,7 @@ AddOptionRTC(hipTailFilterAlpha, float, 0.5f, "", 0, "Smoothing factor for the e AddOptionRTC(occupancyMapTimeBins, uint16_t, 16, "", 0, "Number of timebins per histogram bin of occupancy map (0 = disable occupancy map)") AddOptionRTC(occupancyMapTimeBinsAverage, uint16_t, 0, "", 0, "Number of timebins +/- to use for the averaging") AddOptionRTC(trackFitCovLimit, uint16_t, 1000, "", 0, "Abort fit when y/z cov exceed the limit") -AddOptionRTC(addErrorsCECrossing, uint8_t, 0, "", 0, "Add additional custom track errors when crossing CE, 0 = no custom errors but att 0.5 to sigma_z^2, 1 = only to cov diagonal, 2 = preserve correlations") +AddOptionRTC(addErrorsCECrossing, uint8_t, 0, "", 0, "Add additional custom track errors when crossing CE, 0 = no custom errors but add 0.5 to sigma_z^2, 1 = only to cov diagonal, 2 = preserve correlations") AddOptionRTC(trackMergerMinPartHits, uint8_t, 10, "", 0, "Minimum hits of track part during track merging") AddOptionRTC(trackMergerMinTotalHits, uint8_t, 20, "", 0, "Minimum total of track part during track merging") AddOptionRTC(mergerCERowLimit, uint8_t, 5, "", 0, "Distance from first / last row in order to attempt merging accross CE") @@ -305,7 +305,7 @@ AddHelp("help", 'h') EndConfig() // Scaling factors for gpu buffer size estimation -BeginSubConfig(GPUSettingsProcessingScaling, scaling, configStandalone.proc, "SCALING", 0, "Processing settings for neural network clusterizer", proc_scaling) +BeginSubConfig(GPUSettingsProcessingScaling, scaling, configStandalone.proc, "SCALING", 0, "Memory scaling factor settings", proc_scaling) AddOption(offset, float, 1000., "", 0, "Scaling Factor: offset") AddOption(hitOffset, float, 20000, "", 0, "Scaling Factor: hitOffset") AddOption(tpcPeaksPerDigit, float, 0.2, "", 0, "Scaling Factor: tpcPeaksPerDigit") diff --git a/GPU/GPUTracking/Definitions/Parameters/GPUParameters.csv b/GPU/GPUTracking/Definitions/Parameters/GPUParameters.csv index 5a8c7cf4ddaec..12c87afceb86f 100644 --- a/GPU/GPUTracking/Definitions/Parameters/GPUParameters.csv +++ b/GPU/GPUTracking/Definitions/Parameters/GPUParameters.csv @@ -1,115 +1,117 @@ -Architecture,default,default_cpu,MI100,VEGA,TAHITI,TESLA,FERMI,PASCAL,KEPLER,AMPERE,TURING,ADA,OPENCL,RDNA,MI210,BLACKWELL -,,,,,,,,,,,,,,,, -CORE:,,,,,,,,,,,,,,,, -WARP_SIZE,0,,64,64,32,32,32,32,32,32,32,32,32,32,64,32 -THREAD_COUNT_DEFAULT,256,,256,256,,,,,,512,512,512,256,512,512,512 -,,,,,,,,,,,,,,,, -LB:,,,,,,,,,,,,,,,, -GPUTPCCreateTrackingData,256,,"[256, 7]","[192, 2]",,,,,,384,256,256,,,,384 -GPUTPCTrackletConstructor,256,,"[768, 8]","[512, 10]","[256, 2]","[256, 1]","[256, 2]","[1024, 2]","[512, 4]","[256, 2]","[256, 2]","[256, 2]",,,,768 -GPUTPCTrackletSelector,256,,"[384, 5]","[192, 10]","[256, 3]","[256, 1]","[256, 3]","[512, 4]","[256, 3]","[192, 3]","[192, 3]","[192, 3]",,,,992 -GPUTPCNeighboursFinder,256,,"[192, 8]","[960, 8]",256,256,256,512,256,"[640, 1]","[640, 1]","[640, 1]",,,,992 -GPUTPCNeighboursCleaner,256,,"[128, 5]","[384, 9]",256,256,256,256,256,512,512,512,,,,672 -GPUTPCExtrapolationTracking,256,,"[256, 7]","[256, 2]",,,,,,"[128, 4]","[192, 2]","[192, 2]",,,,896 -GPUTRDTrackerKernels_gpuVersion,512,,,,,,,,,,,,,,, -GPUTPCCreateOccupancyMap_fill,256,,,,,,,,,,,,,,, -GPUTPCCreateOccupancyMap_fold,256,,,,,,,,,,,,,,, -GPUTRDTrackerKernels_o2Version,512,,,,,,,,,,,,,,, -GPUTPCCompressionKernels_step0attached,256,,"[128, 1]","[64, 2]",,,,,,"[64, 2]",128,128,,,,"[96, 3]" -GPUTPCCompressionKernels_step1unattached,256,,"[512, 2]","[512, 2]",,,,,,"[512, 3]","[512, 2]","[512, 2]",,,,"[512, 2]" -GPUTPCDecompressionKernels_step0attached,256,,"[128, 2]","[128, 2]",,,,,,"[32, 1]","[32, 1]","[32, 1]",,,,"[32, 1]" -GPUTPCDecompressionKernels_step1unattached,256,,"[64, 2]","[64, 2]",,,,,,"[32, 1]","[32, 1]","[32, 1]",,,,"[32, 1]" -GPUTPCDecompressionUtilKernels_sortPerSectorRow,256,,,,,,,,,,,,,,, -GPUTPCDecompressionUtilKernels_countFilteredClusters,256,,,,,,,,,,,,,,, -GPUTPCDecompressionUtilKernels_storeFilteredClusters,256,,,,,,,,,,,,,,, -GPUTPCCFDecodeZS,"[128, 4]",,"[64, 4]","[64, 1]",,,,,,"[64, 10]","[64, 8]","[64, 8]",,,,"[64, 10]" -GPUTPCCFDecodeZSLink,"""GPUCA_WARP_SIZE""",,"""GPUCA_WARP_SIZE""","""GPUCA_WARP_SIZE""",,,,,,"""GPUCA_WARP_SIZE""","""GPUCA_WARP_SIZE""","""GPUCA_WARP_SIZE""",,,,"""GPUCA_WARP_SIZE""" -GPUTPCCFDecodeZSDenseLink,"""GPUCA_WARP_SIZE""",,"[""GPUCA_WARP_SIZE"", 4]","[""GPUCA_WARP_SIZE"", 14]",,,,,,"""GPUCA_WARP_SIZE""","""GPUCA_WARP_SIZE""","""GPUCA_WARP_SIZE""",,,,"[""GPUCA_WARP_SIZE"", 8]" -GPUTPCCFGather,"[1024, 1]",,"[1024, 5]","[1024, 1]",,,,,,"[1024, 1]","[1024, 1]","[1024, 1]",,,,"[1024, 1]" -COMPRESSION_GATHER,1024,,1024,1024,,,,,,1024,1024,1024,,,, -GPUTPCGMMergerTrackFit,256,,"[192, 2]","[64, 7]",,,,,,"[64, 4]","[32, 8]","[32, 8]",,,,"[64, 8]" -GPUTPCGMMergerFollowLoopers,256,,"[256, 5]","[256, 4]",,,,,,"[64, 12]","[128, 4]","[128, 4]",,,,"[224, 3]" -GPUTPCGMMergerSectorRefit,256,,"[64, 4]","[256, 2]",,,,,,"[32, 6]","[64, 5]","[64, 5]",,,,"[32, 10]" -GPUTPCGMMergerUnpackResetIds,256,,256,256,,,,,,256,256,256,,,,256 -GPUTPCGMMergerUnpackGlobal,256,,256,256,,,,,,256,256,256,,,,256 -GPUTPCGMMergerResolve_step0,256,,512,256,,,,,,256,256,256,,,,256 -GPUTPCGMMergerResolve_step1,256,,512,256,,,,,,256,256,256,,,,256 -GPUTPCGMMergerResolve_step2,256,,512,256,,,,,,256,256,256,,,,256 -GPUTPCGMMergerResolve_step3,256,,512,256,,,,,,256,256,256,,,,256 -GPUTPCGMMergerResolve_step4,256,,512,256,,,,,,"[256, 4]","[256, 4]","[256, 4]",,,,"[256, 4]" -GPUTPCGMMergerClearLinks,256,,256,256,,,,,,256,256,256,,,,256 -GPUTPCGMMergerMergeWithinPrepare,256,,256,256,,,,,,256,256,256,,,,256 -GPUTPCGMMergerMergeSectorsPrepare,256,,256,256,,,,,,"[256, 2]","[256, 2]","[256, 2]",,,,"[256, 2]" -GPUTPCGMMergerMergeBorders_step0,256,,512,256,,,,,,192,192,192,,,,192 -GPUTPCGMMergerMergeBorders_step2,256,,512,256,,,,,,"[64, 2]",256,256,,,,"[64, 2]" -GPUTPCGMMergerMergeCE,256,,512,256,,,,,,256,256,256,,,,256 -GPUTPCGMMergerLinkExtrapolatedTracks,256,,256,256,,,,,,256,256,256,,,,256 -GPUTPCGMMergerCollect,256,,"[768, 1]","[1024, 1]",,,,,,"[256, 2]","[128, 2]","[128, 2]",,,,"[288, 1]" -GPUTPCGMMergerSortTracksPrepare,256,,256,256,,,,,,256,256,256,,,,256 -GPUTPCGMMergerPrepareForFit_step0,256,,256,256,,,,,,256,256,256,,,,256 -GPUTPCGMMergerPrepareForFit_step1,256,,256,256,,,,,,256,256,256,,,,256 -GPUTPCGMMergerPrepareForFit_step2,256,,256,256,,,,,,256,256,256,,,,256 -GPUTPCGMMergerFinalize_step0,256,,,256,,,,,,,,,,,,256 -GPUTPCGMMergerFinalize_step1,256,,,256,,,,,,,,,,,,256 -GPUTPCGMMergerFinalize_step2,256,,,256,,,,,,,,,,,,256 -GPUTPCGMMergerMergeLoopers_step0,256,,,,,,,,,,,,,,,256 -GPUTPCGMMergerMergeLoopers_step1,256,,,,,,,,,,,,,,,256 -GPUTPCGMMergerMergeLoopers_step2,256,,,,,,,,,,,,,,,256 -GPUTPCGMO2Output_prepare,256,,,,,,,,,,,,,,,256 -GPUTPCGMO2Output_output,256,,,,,,,,,,,,,,,256 -GPUTPCStartHitsFinder,256,,"[1024, 2]","[1024, 7]",256,256,256,256,256,512,512,512,,,,608 -GPUTPCStartHitsSorter,256,,"[1024, 5]","[512, 7]",256,256,256,256,256,"[512, 1]","[512, 1]","[512, 1]",,,,608 -GPUTPCCFCheckPadBaseline,576,,"[576, 2]","[576, 2]",,,,,,"[576, 2]",,,,,,"[576, 2]" -GPUTPCCFHIPTailConnector,256,,256,256,,,,,,256,,,,,, -GPUTPCCFHIPClusterizer,256,,256,256,,,,,,256,,,,,, -GPUTPCCFChargeMapFiller_fillIndexMap,512,,512,512,,,,,,448,,,,,,448 -GPUTPCCFChargeMapFiller_fillFromDigits,512,,512,512,,,,,,448,,,,,,448 -GPUTPCCFChargeMapFiller_findFragmentStart,512,,512,512,,,,,,448,,,,,,448 -GPUTPCCFPeakFinder,512,,"[512, 9]","[512, 4]",,,,,,128,,,,,,"[128, 5]" -GPUTPCCFNoiseSuppression,512,,512,512,,,,,,448,,,,,, -GPUTPCCFDeconvolution,512,,"[512, 5]","[512, 5]",,,,,,384,,,,,,384 -GPUTPCCFClusterizer,512,,"[448, 3]","[512, 2]",,,,,,448,,,,,,"[160, 5]" -GPUTPCNNClusterizerKernels,512,,,,,,,,,,,,,,, -GPUTrackingRefitKernel_mode0asGPU,256,,,,,,,,,,,,,,,256 -GPUTrackingRefitKernel_mode1asTrackParCov,256,,,,,,,,,,,,,,,256 -GPUMemClean16,"[""GPUCA_THREAD_COUNT_DEFAULT"", 1]",,,,,,,,,,,,,,, -GPUitoa,"[""GPUCA_THREAD_COUNT_DEFAULT"", 1]",,,,,,,,,,,,,,, -GPUTPCCFNoiseSuppression_noiseSuppression,"""GPUCA_LB_GPUTPCCFNoiseSuppression""",,,,,,,,,,,,,,,448 -GPUTPCCFNoiseSuppression_updatePeaks,"""GPUCA_LB_GPUTPCCFNoiseSuppression""",,,,,,,,,,,,,,,448 -GPUTPCNNClusterizerKernels_runCfClusterizer,"""GPUCA_LB_GPUTPCCFClusterizer""",,,,,,,,,,,,,,, -GPUTPCNNClusterizerKernels_fillInputNNCPU,"""GPUCA_LB_GPUTPCNNClusterizerKernels""",,,,,,,,,,,,,,, -GPUTPCNNClusterizerKernels_fillInputNNGPU,1024,,,,,,,,,,,,,,, -GPUTPCNNClusterizerKernels_determineClass1Labels,"""GPUCA_LB_GPUTPCNNClusterizerKernels""",,,,,,,,,,,,,,, -GPUTPCNNClusterizerKernels_determineClass2Labels,"""GPUCA_LB_GPUTPCNNClusterizerKernels""",,,,,,,,,,,,,,, -GPUTPCNNClusterizerKernels_publishClass1Regression,"""GPUCA_LB_GPUTPCNNClusterizerKernels""",,,,,,,,,,,,,,, -GPUTPCNNClusterizerKernels_publishClass2Regression,"""GPUCA_LB_GPUTPCNNClusterizerKernels""",,,,,,,,,,,,,,, -GPUTPCNNClusterizerKernels_publishDeconvolutionFlags,"""GPUCA_LB_GPUTPCNNClusterizerKernels""",,,,,,,,,,,,,,, -GPUTPCCFStreamCompaction_scanStart,"""GPUCA_PAR_CF_SCAN_WORKGROUP_SIZE""",,,,,,,,,,,,,,,"""GPUCA_PAR_CF_SCAN_WORKGROUP_SIZE""" -GPUTPCCFStreamCompaction_scanUp,"""GPUCA_PAR_CF_SCAN_WORKGROUP_SIZE""",,,,,,,,,,,,,,,"""GPUCA_PAR_CF_SCAN_WORKGROUP_SIZE""" -GPUTPCCFStreamCompaction_scanTop,"""GPUCA_PAR_CF_SCAN_WORKGROUP_SIZE""",,,,,,,,,,,,,,,"""GPUCA_PAR_CF_SCAN_WORKGROUP_SIZE""" -GPUTPCCFStreamCompaction_scanDown,"""GPUCA_PAR_CF_SCAN_WORKGROUP_SIZE""",,,,,,,,,,,,,,,"""GPUCA_PAR_CF_SCAN_WORKGROUP_SIZE""" -GPUTPCCFStreamCompaction_compactDigits,"""GPUCA_PAR_CF_SCAN_WORKGROUP_SIZE""",,,,,,,,,,,,,,,"""GPUCA_PAR_CF_SCAN_WORKGROUP_SIZE""" -GPUTPCCompressionGatherKernels_unbuffered,"""GPUCA_LB_COMPRESSION_GATHER""",,,,,,,,,,,,,,, -GPUTPCCompressionGatherKernels_buffered32,"""GPUCA_LB_COMPRESSION_GATHER""",,,,,,,,,,,,,,, -GPUTPCCompressionGatherKernels_buffered64,"""GPUCA_LB_COMPRESSION_GATHER""",,,,,,,,,,,,,,, -GPUTPCCompressionGatherKernels_buffered128,"""GPUCA_LB_COMPRESSION_GATHER""",,,,,,,,,,,,,,, -GPUTPCCompressionGatherKernels_multiBlock,"""GPUCA_LB_COMPRESSION_GATHER""",,,,,,,,,,,,,,, -GPUTPCGMMergerFinalize_0,256,,256,,,,,,,256,256,256,,,,256 -GPUTPCGMMergerFinalize_1,256,,256,,,,,,,256,256,256,,,,256 -GPUTPCGMMergerFinalize_2,256,,256,,,,,,,256,256,256,,,,256 -,,,,,,,,,,,,,,,, -PAR:,,,,,,,,,,,,,,,, -AMD_EUS_PER_CU,0,0,4,4,,,,,,,,,,,,0 -SORT_STARTHITS,1,0,,,,,,,,,,,,,,1 -NEIGHBOURS_FINDER_MAX_NNEIGHUP,6,0,10,4,,,,,,4,4,4,,,,2 -NEIGHBOURS_FINDER_UNROLL_GLOBAL,4,0,4,2,,,,,,,,,,,,2 -NEIGHBOURS_FINDER_UNROLL_SHARED,1,0,0,0,,,,,,,,,,,,1 -TRACKLET_SELECTOR_HITS_REG_SIZE,12,0,9,27,,,,,,20,20,20,,,,2 -ALTERNATE_BORDER_SORT,1,0,1,1,,,,,,1,1,1,,,,1 -SORT_BEFORE_FIT,1,0,1,1,,,,,,1,1,1,,,,1 -NO_ATOMIC_PRECHECK,0,0,1,1,,,,,,1,1,1,,,,1 -DEDX_STORAGE_TYPE,"""half""","""float""",,,,,,,,,,,,,, -MERGER_INTERPOLATION_ERROR_TYPE,"""half""","""float""",,,,,,,,,,,,,, -COMP_GATHER_KERNEL,4,0,4,4,,,,,,4,4,4,,,,4 -COMP_GATHER_MODE,3,0,3,3,,,,,,3,3,3,,,,3 -CF_SCAN_WORKGROUP_SIZE,512,0,,,,,,,,,,,,,, +Architecture,default,default_cpu,MI100,VEGA,TAHITI,TESLA,FERMI,PASCAL,KEPLER,AMPERE,TURING,HOPPER,ADA,OPENCL,RDNA,MI210,BLACKWELL,MI300 +,,,,,,,,,,,,,,,,,, +CORE:,,,,,,,,,,,,,,,,,, +WARP_SIZE,0,,64,64,32,32,32,32,32,32,32,32,32,32,32,64,32,64 +THREAD_COUNT_DEFAULT,256,,256,256,,,,,,512,512,,512,256,512,512,512, +,,,,,,,,,,,,,,,,,, +LB:,,,,,,,,,,,,,,,,,, +GPUTPCCreateTrackingData,256,,"[256, 7]","[192, 2]",,,,,,"[224, 7]",256,"[128, 14]",416,,"[64, 21]",,384,"[320, 2]" +GPUTPCTrackletConstructor,256,,"[768, 8]","[512, 10]","[256, 2]","[256, 1]","[256, 2]","[1024, 2]","[512, 4]",1024,"[256, 2]",1024,"[1024, 1]",,"[768, 2]",,768,512 +GPUTPCTrackletSelector,256,,"[384, 5]","[192, 10]","[256, 3]","[256, 1]","[256, 3]","[512, 4]","[256, 3]","[288, 3]","[192, 3]","[544, 1]","[32, 2]",,"[384, 3]",,992,"[256, 6]" +GPUTPCNeighboursFinder,256,,"[192, 8]","[960, 8]",256,256,256,512,256,864,"[640, 1]","[512, 2]","[736, 1]",,"[480, 3]",,992,"[704, 1]" +GPUTPCNeighboursCleaner,256,,"[128, 5]","[384, 9]",256,256,256,256,256,544,512,"[192, 9]","[512, 1]",,"[384, 5]",,672,"[640, 1]" +GPUTPCExtrapolationTracking,256,,"[256, 7]","[256, 2]",,,,,,"[352, 4]","[192, 2]","[896, 1]","[352, 1]",,"[1024, 1]",,896,1024 +GPUTRDTrackerKernels_gpuVersion,512,,,,,,,,,512,,512,512,,512,,,512 +GPUTPCCreateOccupancyMap_fill,256,,,,,,,,,256,,256,256,,256,,,256 +GPUTPCCreateOccupancyMap_fold,256,,,,,,,,,256,,256,256,,256,,,256 +GPUTRDTrackerKernels_o2Version,512,,,,,,,,,512,,512,512,,512,,,512 +GPUTPCCompressionKernels_step0attached,256,,"[128, 1]","[64, 2]",,,,,,"[160, 2]",128,"[448, 1]",352,,"[1024, 1]",,"[96, 3]","[128, 4]" +GPUTPCCompressionKernels_step1unattached,256,,"[512, 2]","[512, 2]",,,,,,"[288, 4]","[512, 2]","[256, 4]","[512, 2]",,"[512, 3]",,"[512, 2]","[512, 3]" +GPUTPCDecompressionKernels_step0attached,256,,"[128, 2]","[128, 2]",,,,,,"[32, 1]","[32, 1]","[32, 1]","[32, 1]",,"[128, 1]",,"[32, 1]","[128, 1]" +GPUTPCDecompressionKernels_step1unattached,256,,"[64, 2]","[64, 2]",,,,,,"[32, 1]","[32, 1]","[32, 1]","[32, 1]",,"[64, 1]",,"[32, 1]","[64, 1]" +GPUTPCDecompressionUtilKernels_sortPerSectorRow,256,,,,,,,,,256,,256,256,,256,,,256 +GPUTPCDecompressionUtilKernels_countFilteredClusters,256,,,,,,,,,256,,256,256,,256,,,256 +GPUTPCDecompressionUtilKernels_storeFilteredClusters,256,,,,,,,,,256,,256,256,,256,,,256 +GPUTPCCFDecodeZS,"[128, 4]",,"[64, 4]","[64, 1]",,,,,,"[32, 10]","[64, 8]","[32, 10]","[32, 10]",,"[64, 1]",,"[64, 10]","[64, 1]" +GPUTPCCFDecodeZSLink,"""GPUCA_WARP_SIZE""",,"""GPUCA_WARP_SIZE""","""GPUCA_WARP_SIZE""",,,,,,"""GPUCA_WARP_SIZE""","""GPUCA_WARP_SIZE""","""GPUCA_WARP_SIZE""","""GPUCA_WARP_SIZE""",,64,,"""GPUCA_WARP_SIZE""","""GPUCA_WARP_SIZE""" +GPUTPCCFDecodeZSDenseLink,"""GPUCA_WARP_SIZE""",,"[""GPUCA_WARP_SIZE"", 4]","[""GPUCA_WARP_SIZE"", 14]",,,,,,"[""GPUCA_WARP_SIZE"", 14]","""GPUCA_WARP_SIZE""","[""GPUCA_WARP_SIZE"", 22]","[""GPUCA_WARP_SIZE"", 22]",,"[64, 17]",,"[""GPUCA_WARP_SIZE"", 8]","[""GPUCA_WARP_SIZE"", 5]" +GPUTPCCFGather,"[1024, 1]",,"[1024, 5]","[1024, 1]",,,,,,"[160, 11]","[1024, 1]",736,896,,"[928, 1]",,"[1024, 1]","[320, 2]" +COMPRESSION_GATHER,1024,,1024,1024,,,,,,1024,1024,,1024,,,,, +GPUTPCGMMergerTrackFit,256,,"[192, 2]","[64, 7]",,,,,,"[32, 16]","[32, 8]","[32, 14]","[160, 2]",,"[32, 24]",,"[64, 8]","[64, 6]" +GPUTPCGMMergerFollowLoopers,256,,"[256, 5]","[256, 4]",,,,,,"[256, 4]","[128, 4]","[1024, 1]",640,,"[128, 16]",,"[224, 3]","[256, 7]" +GPUTPCGMMergerSectorRefit,256,,"[64, 4]","[256, 2]",,,,,,"[32, 8]","[64, 5]","[32, 7]","[32, 7]",,"[32, 20]",,"[32, 10]","[64, 4]" +GPUTPCGMMergerUnpackResetIds,256,,256,256,,,,,,256,256,256,256,,256,,256,256 +GPUTPCGMMergerUnpackGlobal,256,,256,256,,,,,,256,256,256,256,,256,,256,256 +GPUTPCGMMergerResolve_step0,256,,512,256,,,,,,256,256,256,256,,256,,256,256 +GPUTPCGMMergerResolve_step1,256,,512,256,,,,,,256,256,256,256,,256,,256,256 +GPUTPCGMMergerResolve_step2,256,,512,256,,,,,,256,256,256,256,,256,,256,256 +GPUTPCGMMergerResolve_step3,256,,512,256,,,,,,256,256,256,256,,256,,256,256 +GPUTPCGMMergerResolve_step4,256,,512,256,,,,,,"[256, 4]","[256, 4]","[256, 4]","[256, 4]",,256,,"[256, 4]",256 +GPUTPCGMMergerClearLinks,256,,256,256,,,,,,256,256,256,256,,256,,256,256 +GPUTPCGMMergerMergeWithinPrepare,256,,256,256,,,,,,256,256,256,256,,256,,256,256 +GPUTPCGMMergerMergeSectorsPrepare,256,,256,256,,,,,,"[256, 2]","[256, 2]","[256, 2]","[256, 2]",,256,,"[256, 2]",256 +GPUTPCGMMergerMergeBorders_step0,256,,512,256,,,,,,192,192,192,192,,256,,192,256 +GPUTPCGMMergerMergeBorders_step2,256,,512,256,,,,,,"[64, 2]",256,"[64, 2]","[64, 2]",,256,,"[64, 2]",256 +GPUTPCGMMergerMergeCE,256,,512,256,,,,,,256,256,256,256,,256,,256,256 +GPUTPCGMMergerLinkExtrapolatedTracks,256,,256,256,,,,,,256,256,256,256,,256,,256,256 +GPUTPCGMMergerCollect,256,,"[768, 1]","[1024, 1]",,,,,,"[864, 1]","[128, 2]","[896, 1]",128,,1024,,"[288, 1]","[384, 4]" +GPUTPCGMMergerSortTracksPrepare,256,,256,256,,,,,,256,256,256,256,,256,,256,256 +GPUTPCGMMergerPrepareForFit_step0,256,,256,256,,,,,,256,256,256,256,,256,,256,256 +GPUTPCGMMergerPrepareForFit_step1,256,,256,256,,,,,,256,256,256,256,,256,,256,256 +GPUTPCGMMergerPrepareForFit_step2,256,,256,256,,,,,,256,256,256,256,,256,,256,256 +GPUTPCGMMergerFinalize_step0,256,,,256,,,,,,256,,256,256,,256,,256,256 +GPUTPCGMMergerFinalize_step1,256,,,256,,,,,,256,,256,256,,256,,256,256 +GPUTPCGMMergerFinalize_step2,256,,,256,,,,,,256,,256,256,,256,,256,256 +GPUTPCGMMergerMergeLoopers_step0,256,,,,,,,,,256,,256,256,,256,,256,256 +GPUTPCGMMergerMergeLoopers_step1,256,,,,,,,,,256,,256,256,,256,,256,256 +GPUTPCGMMergerMergeLoopers_step2,256,,,,,,,,,256,,256,256,,256,,256,256 +GPUTPCGMO2Output_prepare,256,,,,,,,,,256,,256,256,,256,,256,256 +GPUTPCGMO2Output_output,256,,,,,,,,,256,,256,256,,256,,256,256 +GPUTPCStartHitsFinder,256,,"[1024, 2]","[1024, 7]",256,256,256,256,256,"[224, 1]",512,"[416, 4]",928,,"[320, 5]",,608,"[448, 3]" +GPUTPCStartHitsSorter,256,,"[1024, 5]","[512, 7]",256,256,256,256,256,"[320, 2]","[512, 1]","[864, 1]","[96, 2]",,"[192, 5]",,608,"[448, 1]" +GPUTPCCFCheckPadBaseline,576,,"[576, 2]","[576, 2]",,,,,,"[576, 3]",,"[576, 1]","[576, 1]",,"[576, 2]",,"[576, 2]",576 +GPUTPCCFHIPTailConnector,256,,256,256,,,,,,"[224, 2]",,"[320, 5]","[704, 1]",,"[128, 7]",,,"[448, 4]" +GPUTPCCFHIPClusterizer,256,,256,256,,,,,,"[288, 5]",,"[480, 3]","[448, 3]",,352,,,"[512, 3]" +GPUTPCCFChargeMapFiller_fillIndexMap,512,,512,512,,,,,,448,,448,448,,512,,448,512 +GPUTPCCFChargeMapFiller_fillFromDigits,512,,512,512,,,,,,448,,448,448,,512,,448,512 +GPUTPCCFChargeMapFiller_findFragmentStart,512,,512,512,,,,,,448,,448,448,,512,,448,512 +GPUTPCCFPeakFinder,512,,"[512, 9]","[512, 4]",,,,,,416,,992,"[672, 1]",,"[384, 2]",,"[128, 5]","[192, 10]" +GPUTPCCFNoiseSuppression,512,,512,512,,,,,,608,,896,480,,160,,,448 +GPUTPCCFDeconvolution,512,,"[512, 5]","[512, 5]",,,,,,"[480, 4]",,224,512,,480,,384,"[448, 3]" +GPUTPCCFClusterizer,512,,"[448, 3]","[512, 2]",,,,,,"[608, 3]",,736,"[192, 3]",,576,,"[160, 5]","[832, 2]" +GPUTPCNNClusterizerKernels,512,,,,,,,,,,,,,,,,, +GPUTrackingRefitKernel_mode0asGPU,256,,,,,,,,,256,,256,256,,256,,256,256 +GPUTrackingRefitKernel_mode1asTrackParCov,256,,,,,,,,,256,,256,256,,256,,256,256 +GPUMemClean16,"[""GPUCA_THREAD_COUNT_DEFAULT"", 1]",,,,,,,,,"[512, 1]",,"[512, 1]","[512, 1]",,"[256, 1]",,,"[256, 1]" +GPUitoa,"[""GPUCA_THREAD_COUNT_DEFAULT"", 1]",,,,,,,,,"[512, 1]",,"[512, 1]","[512, 1]",,"[256, 1]",,,"[256, 1]" +GPUTPCCFNoiseSuppression_noiseSuppression,"""GPUCA_LB_GPUTPCCFNoiseSuppression""",,,,,,,,,,,,,,,,448, +GPUTPCCFNoiseSuppression_updatePeaks,"""GPUCA_LB_GPUTPCCFNoiseSuppression""",,,,,,,,,,,,,,,,448, +GPUTPCNNClusterizerKernels_runCfClusterizer,"""GPUCA_LB_GPUTPCCFClusterizer""",,,,,,,,,,,,,,,,, +GPUTPCNNClusterizerKernels_fillInputNNCPU,"""GPUCA_LB_GPUTPCNNClusterizerKernels""",,,,,,,,,,,,,,,,, +GPUTPCNNClusterizerKernels_fillInputNNGPU,1024,,,,,,,,,,,,,,,,, +GPUTPCNNClusterizerKernels_determineClass1Labels,"""GPUCA_LB_GPUTPCNNClusterizerKernels""",,,,,,,,,,,,,,,,, +GPUTPCNNClusterizerKernels_determineClass2Labels,"""GPUCA_LB_GPUTPCNNClusterizerKernels""",,,,,,,,,,,,,,,,, +GPUTPCNNClusterizerKernels_publishClass1Regression,"""GPUCA_LB_GPUTPCNNClusterizerKernels""",,,,,,,,,,,,,,,,, +GPUTPCNNClusterizerKernels_publishClass2Regression,"""GPUCA_LB_GPUTPCNNClusterizerKernels""",,,,,,,,,,,,,,,,, +GPUTPCNNClusterizerKernels_publishDeconvolutionFlags,"""GPUCA_LB_GPUTPCNNClusterizerKernels""",,,,,,,,,,,,,,,,, +GPUTPCCFStreamCompaction_scanStart,"""GPUCA_PAR_CF_SCAN_WORKGROUP_SIZE""",,,,,,,,,,,,,,,,, +GPUTPCCFStreamCompaction_scanUp,"""GPUCA_PAR_CF_SCAN_WORKGROUP_SIZE""",,,,,,,,,,,,,,,,, +GPUTPCCFStreamCompaction_scanTop,"""GPUCA_PAR_CF_SCAN_WORKGROUP_SIZE""",,,,,,,,,,,,,,,,, +GPUTPCCFStreamCompaction_scanDown,"""GPUCA_PAR_CF_SCAN_WORKGROUP_SIZE""",,,,,,,,,,,,,,,,, +GPUTPCCFStreamCompaction_compactDigits,"""GPUCA_PAR_CF_SCAN_WORKGROUP_SIZE""",,,,,,,,,,,,,,,,, +GPUTPCCompressionGatherKernels_unbuffered,"""GPUCA_LB_COMPRESSION_GATHER""",,,,,,,,,,,,,,,,, +GPUTPCCompressionGatherKernels_buffered32,"""GPUCA_LB_COMPRESSION_GATHER""",,,,,,,,,,,,,,,,, +GPUTPCCompressionGatherKernels_buffered64,"""GPUCA_LB_COMPRESSION_GATHER""",,,,,,,,,,,,,,,,, +GPUTPCCompressionGatherKernels_buffered128,"""GPUCA_LB_COMPRESSION_GATHER""",,,,,,,,,,,,,,,,, +GPUTPCCompressionGatherKernels_multiBlock,"""GPUCA_LB_COMPRESSION_GATHER""",,,,,,,,,,,,,,,,, +GPUTPCGMMergerFinalize_0,256,,256,,,,,,,256,256,,256,,,,256, +GPUTPCGMMergerFinalize_1,256,,256,,,,,,,256,256,,256,,,,256, +GPUTPCGMMergerFinalize_2,256,,256,,,,,,,256,256,,256,,,,256, +GPUTPCConvertKernel,,,,,,,,,,,,,,,256,,,256 +,,,,,,,,,,,,,,,,,, +PAR:,,,,,,,,,,,,,,,,,, +AMD_EUS_PER_CU,0,0,4,4,,,,,,,,,,,4,,0,4 +SORT_STARTHITS,1,0,,,,,,,,1,,1,1,,1,,1,1 +NEIGHBOURS_FINDER_MAX_NNEIGHUP,6,0,10,4,,,,,,4,4,4,4,,5,,2,5 +NEIGHBOURS_FINDER_UNROLL_GLOBAL,4,0,4,2,,,,,,2,,8,8,,4,,2,2 +NEIGHBOURS_FINDER_UNROLL_SHARED,1,0,0,0,,,,,,1,,1,0,,1,,1,1 +TRACKLET_SELECTOR_HITS_REG_SIZE,12,0,9,27,,,,,,20,20,20,20,,20,,2,20 +ALTERNATE_BORDER_SORT,1,0,1,1,,,,,,1,1,1,1,,1,,1,1 +SORT_BEFORE_FIT,1,0,1,1,,,,,,1,1,1,1,,1,,1,1 +NO_ATOMIC_PRECHECK,0,0,1,1,,,,,,1,1,1,1,,1,,1,1 +DEDX_STORAGE_TYPE,"""half""","""float""",,,,,,,,,,,,,,,, +MERGER_INTERPOLATION_ERROR_TYPE,"""half""","""float""",,,,,,,,,,,,,,,, +COMP_GATHER_KERNEL,4,0,4,4,,,,,,4,4,4,4,,4,,4,4 +COMP_GATHER_MODE,3,0,3,3,,,,,,3,3,3,3,,3,,3,3 +CF_SCAN_WORKGROUP_SIZE,512,0,,,,,,,,224,,992,448,,1024,,,448 +MERGER_SPLIT_LOOP_INTERPOLATION,,,,,,,,,,,,,,,1,,,1 diff --git a/GPU/GPUTracking/Global/GPUChainTracking.cxx b/GPU/GPUTracking/Global/GPUChainTracking.cxx index 067c4dff92810..480ec76408584 100644 --- a/GPU/GPUTracking/Global/GPUChainTracking.cxx +++ b/GPU/GPUTracking/Global/GPUChainTracking.cxx @@ -677,7 +677,7 @@ int32_t GPUChainTracking::RunChain() const bool needQA = GPUQA::QAAvailable() && (GetProcessingSettings().runQA || (GetProcessingSettings().eventDisplay && (mIOPtrs.nMCInfosTPC || GetProcessingSettings().runMC))); if (needQA && GetQA()->IsInitialized() == false) { if (GetQA()->InitQA(GetProcessingSettings().runQA <= 0 ? -GetProcessingSettings().runQA : gpudatatypes::gpuqa::tasksAutomatic)) { - return 1; + return GPUReconstruction::retValValue::error; } } if (needQA) { @@ -693,7 +693,7 @@ int32_t GPUChainTracking::RunChain() mRec->PrepareEvent(); } catch (const std::bad_alloc& e) { GPUError("Memory Allocation Error"); - return (1); + return GPUReconstruction::retValValue::error; } mRec->getGeneralStepTimer(GeneralStep::Prepare).Stop(); @@ -707,11 +707,11 @@ int32_t GPUChainTracking::RunChain() if (mIOPtrs.tpcCompressedClusters) { if (runRecoStep(RecoStep::TPCDecompression, &GPUChainTracking::RunTPCDecompression)) { - return 1; + return GPUReconstruction::retValValue::error; } } else if (mIOPtrs.tpcPackedDigits || mIOPtrs.tpcZS) { if (runRecoStep(RecoStep::TPCClusterFinding, &GPUChainTracking::RunTPCClusterizer, false)) { - return 1; + return GPUReconstruction::retValValue::error; } } @@ -720,17 +720,17 @@ int32_t GPUChainTracking::RunChain() } if (mIOPtrs.clustersNative && runRecoStep(RecoStep::TPCConversion, &GPUChainTracking::ConvertNativeToClusterData)) { - return 1; + return GPUReconstruction::retValValue::error; } mRec->PushNonPersistentMemory(qStr2Tag("TPCSLCD1")); // 1st stack level for TPC tracking sector data mTPCSectorScratchOnStack = true; if (runRecoStep(RecoStep::TPCSectorTracking, &GPUChainTracking::RunTPCTrackingSectors)) { - return 1; + return GPUReconstruction::retValValue::error; } if (runRecoStep(RecoStep::TPCMerging, &GPUChainTracking::RunTPCTrackingMerger, false)) { - return 1; + return GPUReconstruction::retValValue::error; } if (mTPCSectorScratchOnStack) { mRec->PopNonPersistentMemory(RecoStep::TPCSectorTracking, qStr2Tag("TPCSLCD1")); // Release 1st stack level, TPC sector data not needed after merger @@ -750,16 +750,16 @@ int32_t GPUChainTracking::RunChain() } } if (runRecoStep(RecoStep::TPCCompression, &GPUChainTracking::RunTPCCompression)) { - return 1; + return GPUReconstruction::retValValue::error; } } if (runRecoStep(RecoStep::TRDTracking, &GPUChainTracking::RunTRDTracking)) { - return 1; + return GPUReconstruction::retValValue::error; } if (runRecoStep(RecoStep::Refit, &GPUChainTracking::RunRefit)) { - return 1; + return GPUReconstruction::retValValue::error; } if (!GetProcessingSettings().doublePipeline) { // Synchronize with output copies running asynchronously @@ -770,9 +770,9 @@ int32_t GPUChainTracking::RunChain() mRec->SetNActiveThreads(-1); } - int32_t retVal = 0; + int32_t retVal = GPUReconstruction::retValValue::ok; if (CheckErrorCodes(false, false, mRec->getErrorCodeOutput())) { // TODO: Eventually, we should use GPUReconstruction::CheckErrorCodes - retVal = 3; + retVal = GPUReconstruction::retValValue::nonFatalErrorCode; if (!GetProcessingSettings().ignoreNonFatalGPUErrors) { return retVal; } @@ -820,7 +820,7 @@ int32_t GPUChainTracking::RunChainFinalize() GPUInfo("Starting Event Display..."); if (mEventDisplay->StartDisplay()) { GPUError("Error starting Event Display"); - return (1); + return GPUReconstruction::retValValue::error; } mDisplayRunning = true; } else { @@ -857,7 +857,7 @@ int32_t GPUChainTracking::RunChainFinalize() mDisplayRunning = false; GetProcessingSettings().eventDisplay->DisplayExit(); const_cast(GetProcessingSettings()).eventDisplay = nullptr; // TODO: fixme - eventDisplay should probably not be put into ProcessingSettings in the first place - return (2); + return GPUReconstruction::retValValue::doExit; } GetProcessingSettings().eventDisplay->setDisplayControl(0); GPUInfo("Loading next event..."); @@ -865,7 +865,7 @@ int32_t GPUChainTracking::RunChainFinalize() mEventDisplay->BlockTillNextEvent(); } - return 0; + return GPUReconstruction::retValValue::ok; } int32_t GPUChainTracking::FinalizePipelinedProcessing() diff --git a/GPU/GPUTracking/Global/GPUChainTracking.h b/GPU/GPUTracking/Global/GPUChainTracking.h index 3c3531530372a..759aaf818028e 100644 --- a/GPU/GPUTracking/Global/GPUChainTracking.h +++ b/GPU/GPUTracking/Global/GPUChainTracking.h @@ -195,7 +195,7 @@ class GPUChainTracking : public GPUChain void SetCalibObjects(const GPUCalibObjects& obj); void SetUpdateCalibObjects(const GPUCalibObjectsConst& obj, const GPUNewCalibValues& vals); void SetSubOutputControl(int32_t i, GPUOutputControl* v) { mSubOutputControls[i] = v; } - void SetFinalInputCallback(std::function v) { mWaitForFinalInputs = v; } + void SetFinalInputCallback(std::function v) { mWaitForFinalInputs = v; } const GPUSettingsDisplay* mConfigDisplay = nullptr; // Abstract pointer to Standalone Display Configuration Structure const GPUSettingsQA* mConfigQA = nullptr; // Abstract pointer to Standalone QA Configuration Structure @@ -321,7 +321,7 @@ class GPUChainTracking : public GPUChain std::mutex mMutexUpdateCalib; std::unique_ptr mPipelineFinalizationCtx; GPUChainTrackingFinalContext* mPipelineNotifyCtx = nullptr; - std::function mWaitForFinalInputs; + std::function mWaitForFinalInputs; int32_t OutputStream() const { return mRec->NStreams() - 2; } }; diff --git a/GPU/GPUTracking/Global/GPUChainTrackingClusterizer.cxx b/GPU/GPUTracking/Global/GPUChainTrackingClusterizer.cxx index 681914414d401..a558ed85c5516 100644 --- a/GPU/GPUTracking/Global/GPUChainTrackingClusterizer.cxx +++ b/GPU/GPUTracking/Global/GPUChainTrackingClusterizer.cxx @@ -769,7 +769,7 @@ int32_t GPUChainTracking::RunTPCClusterizer(bool synchronizeOutput) #endif if (RunTPCClusterizer_prepare(mPipelineNotifyCtx && GetProcessingSettings().doublePipelineClusterizer, extraADCs)) { - return 1; + return GPUReconstruction::retValValue::error; } if (GetProcessingSettings().autoAdjustHostThreads && !doGPU) { mRec->SetNActiveThreads(mRec->MemoryScalers()->nTPCdigits / 6000); @@ -1471,7 +1471,9 @@ int32_t GPUChainTracking::RunTPCClusterizer(bool synchronizeOutput) notifyForeignChainFinished(); } if (mWaitForFinalInputs && iSectorBase >= 30 && (int32_t)iSectorBase < 30 + GetProcessingSettings().nTPCClustererLanes) { - mWaitForFinalInputs(); + if (mWaitForFinalInputs()) { + return GPUReconstruction::retValValue::abort; + } synchronizeCalibUpdate = DoQueuedUpdates(0, false); } } diff --git a/GPU/GPUTracking/Interface/GPUO2Interface.cxx b/GPU/GPUTracking/Interface/GPUO2Interface.cxx index ced3016dc15b1..184597b12ceba 100644 --- a/GPU/GPUTracking/Interface/GPUO2Interface.cxx +++ b/GPU/GPUTracking/Interface/GPUO2Interface.cxx @@ -137,6 +137,11 @@ void GPUO2Interface::Deinitialize() mNContexts = 0; } +void GPUO2Interface::DrainPipeline() +{ + mCtx[0].mRec->DrainPipeline(); +} + void GPUO2Interface::DumpEvent(int32_t nEvent, GPUTrackingInOutPointers* data, uint32_t iThread, const char* dir) { const auto oldPtrs = mCtx[iThread].mChain->mIOPtrs; @@ -185,19 +190,23 @@ int32_t GPUO2Interface::RunTracking(GPUTrackingInOutPointers* data, GPUInterface } }; - auto inputWaitCallback = [this, iThread, inputUpdateCallback, &data, &outputs, &setOutputs]() { + auto inputWaitCallback = [this, iThread, inputUpdateCallback, &data, &outputs, &setOutputs]() -> int32_t { GPUTrackingInOutPointers* updatedData; GPUInterfaceOutputs* updatedOutputs; + int32_t retVal = 0; if (inputUpdateCallback->callback) { - inputUpdateCallback->callback(updatedData, updatedOutputs); - mCtx[iThread].mChain->mIOPtrs = *updatedData; - outputs = updatedOutputs; - data = updatedData; - setOutputs(outputs); + retVal = inputUpdateCallback->callback(updatedData, updatedOutputs); + if (retVal == 0) { + mCtx[iThread].mChain->mIOPtrs = *updatedData; + outputs = updatedOutputs; + data = updatedData; + setOutputs(outputs); + } } if (inputUpdateCallback->notifyCallback) { inputUpdateCallback->notifyCallback(); } + return retVal; }; if (inputUpdateCallback) { @@ -210,8 +219,8 @@ int32_t GPUO2Interface::RunTracking(GPUTrackingInOutPointers* data, GPUInterface } int32_t retVal = mCtx[iThread].mRec->RunChains(); - if (retVal == 2) { - retVal = 0; // 2 signals end of event display, ignore + if (retVal == GPUReconstruction::retValValue::doExit) { + retVal = GPUReconstruction::retValValue::ok; // Ignore exit signal from event display } if (mConfig->configQA.shipToQC && mCtx[iThread].mChain->QARanForTF()) { outputs->qa.hist1 = &mCtx[iThread].mChain->GetQA()->getHistograms1D(); diff --git a/GPU/GPUTracking/Interface/GPUO2Interface.h b/GPU/GPUTracking/Interface/GPUO2Interface.h index ca56018908b41..eed7c119f5328 100644 --- a/GPU/GPUTracking/Interface/GPUO2Interface.h +++ b/GPU/GPUTracking/Interface/GPUO2Interface.h @@ -71,6 +71,7 @@ class GPUO2Interface int32_t Initialize(const GPUO2InterfaceConfiguration& config); void Deinitialize(); + void DrainPipeline(); int32_t RunTracking(GPUTrackingInOutPointers* data, GPUInterfaceOutputs* outputs = nullptr, uint32_t iThread = 0, GPUInterfaceInputUpdate* inputUpdateCallback = nullptr); void Clear(bool clearOutputs, uint32_t iThread = 0); diff --git a/GPU/GPUTracking/Interface/GPUO2InterfaceConfiguration.h b/GPU/GPUTracking/Interface/GPUO2InterfaceConfiguration.h index 0f8a3784f0a88..155048859a507 100644 --- a/GPU/GPUTracking/Interface/GPUO2InterfaceConfiguration.h +++ b/GPU/GPUTracking/Interface/GPUO2InterfaceConfiguration.h @@ -58,8 +58,8 @@ struct GPUInterfaceOutputs : public GPUTrackingOutputs { }; struct GPUInterfaceInputUpdate { - std::function callback; // Callback which provides final data ptrs / outputRegions after Clusterization stage - std::function notifyCallback; // Callback called to notify that Clusterization state has finished without update + std::function callback; // Callback which provides final data ptrs / outputRegions after Clusterization stage + std::function notifyCallback; // Callback called to notify that Clusterization state has finished without update }; // Full configuration structure with all available settings of GPU... diff --git a/GPU/GPUTracking/Standalone/Benchmark/standalone.cxx b/GPU/GPUTracking/Standalone/Benchmark/standalone.cxx index a9fed2d4c9897..c452e973d97e0 100644 --- a/GPU/GPUTracking/Standalone/Benchmark/standalone.cxx +++ b/GPU/GPUTracking/Standalone/Benchmark/standalone.cxx @@ -685,12 +685,11 @@ int32_t RunBenchmark(GPUReconstruction* recUse, GPUChainTracking* chainTrackingU } } - if (tmpRetVal == 0 || tmpRetVal == 2) { + if (tmpRetVal == GPUReconstruction::retValValue::ok || tmpRetVal == GPUReconstruction::retValValue::doExit) { OutputStat(chainTrackingUse, iRun == 0 ? nTracksTotal : nullptr, iRun == 0 ? nClustersTotal : nullptr); } - if (tmpRetVal == 0 && configStandalone.testSyncAsync) { - + if (tmpRetVal == GPUReconstruction::retValValue::ok && configStandalone.testSyncAsync) { vecpod compressedTmpMem(chainTracking->mIOPtrs.tpcCompressedClusters->totalDataSize); memcpy(compressedTmpMem.data(), (const void*)chainTracking->mIOPtrs.tpcCompressedClusters, chainTracking->mIOPtrs.tpcCompressedClusters->totalDataSize); o2::tpc::CompressedClusters tmp(*chainTracking->mIOPtrs.tpcCompressedClusters); @@ -718,7 +717,7 @@ int32_t RunBenchmark(GPUReconstruction* recUse, GPUChainTracking* chainTrackingU recAsync->SetResetTimers(iRun < configStandalone.runsInit); } tmpRetVal = recAsync->RunChains(); - if (tmpRetVal == 0 || tmpRetVal == 2) { + if (tmpRetVal == GPUReconstruction::retValValue::ok || tmpRetVal == GPUReconstruction::retValValue::doExit) { OutputStat(chainTrackingAsync, nullptr, nullptr); } recAsync->ClearAllocatedMemory(); @@ -727,14 +726,14 @@ int32_t RunBenchmark(GPUReconstruction* recUse, GPUChainTracking* chainTrackingU recUse->ClearAllocatedMemory(); } - if (tmpRetVal == 2) { + if (tmpRetVal == GPUReconstruction::retValValue::doExit) { configStandalone.continueOnError = 0; // Forced exit from event display loop configStandalone.noprompt = 1; } - if (tmpRetVal == 3 && configStandalone.proc.ignoreNonFatalGPUErrors) { + if (tmpRetVal == GPUReconstruction::retValValue::nonFatalErrorCode && configStandalone.proc.ignoreNonFatalGPUErrors) { printf("GPU Standalone Benchmark: Non-FATAL GPU error occured, ignoring\n"); } else if (tmpRetVal && !configStandalone.continueOnError) { - if (tmpRetVal != 2) { + if (tmpRetVal != GPUReconstruction::retValValue::doExit) { printf("GPU Standalone Benchmark: Error occured\n"); } return 1; diff --git a/GPU/GPUTracking/Standalone/CMakeLists.txt b/GPU/GPUTracking/Standalone/CMakeLists.txt index 0c04f5e562fef..a4c5817d1f7f9 100644 --- a/GPU/GPUTracking/Standalone/CMakeLists.txt +++ b/GPU/GPUTracking/Standalone/CMakeLists.txt @@ -107,7 +107,7 @@ if(GPUCA_BUILD_EVENT_DISPLAY) set(Vulkan_FOUND OFF) endif() if(GPUCA_BUILD_EVENT_DISPLAY_QT) - find_package(Qt5 COMPONENTS Widgets REQUIRED) + find_package(Qt6 COMPONENTS Widgets REQUIRED) endif() else() set(OpenGL_FOUND OFF) diff --git a/GPU/GPUTracking/TRDTracking/GPUTRDTracker.cxx b/GPU/GPUTracking/TRDTracking/GPUTRDTracker.cxx index 324acc9451f36..f5f8f08b1138e 100644 --- a/GPU/GPUTracking/TRDTracking/GPUTRDTracker.cxx +++ b/GPU/GPUTracking/TRDTracking/GPUTRDTracker.cxx @@ -1056,7 +1056,7 @@ GPUd() void GPUTRDTracker_t::RecalcTrkltCovDy(const float tilt, co { float t2 = tilt * tilt; // tan^2 (tilt) float c2 = 1.f / (1.f + t2); // cos^2 (tilt) - float sy2 = mRecoParam->getRPhiRes(snp, CAMath::Abs(pull), occupancy); + // float sy2 = mRecoParam->getRPhiRes(snp, CAMath::Abs(pull), occupancy); float sdy2 = mRecoParam->getDyRes(snp, occupancy); cov[3] = mRecoParam->getCorrYDy() * CAMath::Sqrt(sdy2 * c2); cov[4] = -tilt * mRecoParam->getCorrYDy() * CAMath::Sqrt(sdy2 * c2); diff --git a/GPU/GPUTracking/display/CMakeLists.txt b/GPU/GPUTracking/display/CMakeLists.txt index 82ce0d4a9b190..eb7a0dc6d6400 100644 --- a/GPU/GPUTracking/display/CMakeLists.txt +++ b/GPU/GPUTracking/display/CMakeLists.txt @@ -26,8 +26,8 @@ if(ALIGPU_BUILD_TYPE STREQUAL "O2") set_package_properties(Fontconfig PROPERTIES TYPE OPTIONAL) find_package(O2GPUWayland) set_package_properties(O2GPUWayland PROPERTIES TYPE OPTIONAL) - find_package(Qt5 COMPONENTS Widgets) - set_package_properties(Qt5 PROPERTIES TYPE OPTIONAL) + find_package(Qt6 COMPONENTS Widgets) + set_package_properties(Qt6 PROPERTIES TYPE OPTIONAL) endif() if(Vulkan_FOUND) @@ -46,7 +46,7 @@ endif() if(Freetype_FOUND) set(GPUCA_EVENT_DISPLAY_FREETYPE ON) endif() -if(Qt5_FOUND) +if(Qt6_FOUND) set(GPUCA_EVENT_DISPLAY_QT ON) endif() @@ -222,7 +222,7 @@ endif() if(GPUCA_EVENT_DISPLAY_QT) target_compile_definitions(${targetName} PRIVATE GPUCA_BUILD_EVENT_DISPLAY_QT) - target_link_libraries(${targetName} PRIVATE Qt5::Widgets) + target_link_libraries(${targetName} PRIVATE Qt6::Widgets) endif() target_link_libraries(${targetName} PRIVATE TBB::tbb) diff --git a/GPU/TPCFastTransformation/CMakeLists.txt b/GPU/TPCFastTransformation/CMakeLists.txt index c4fb7c04796f2..d48f7660c45b9 100644 --- a/GPU/TPCFastTransformation/CMakeLists.txt +++ b/GPU/TPCFastTransformation/CMakeLists.txt @@ -88,6 +88,22 @@ if(${ALIGPU_BUILD_TYPE} STREQUAL "O2") ${CMAKE_BINARY_DIR}/stage/include LABELS gpu tpc COMPILE_ONLY) endforeach() + + o2_add_test_root_macro(macro/TPCFastTransformInitCPM.C + PUBLIC_LINK_LIBRARIES O2::TPCFastTransformation + O2::DataFormatsTPC + O2::TPCSimulation + O2::TPCReconstruction + O2::TPCCalibration + O2::SpacePoints + O2::CommonUtils + O2::MathUtils + O2::Algorithm + O2::Framework + PUBLIC_INCLUDE_DIRECTORIES + ${CMAKE_BINARY_DIR}/stage/include + LABELS gpu tpc COMPILE_ONLY) + foreach(m IrregularSpline1DTest.C IrregularSpline2D3DCalibratorTest.C @@ -107,6 +123,9 @@ if(${ALIGPU_BUILD_TYPE} STREQUAL "O2") install(FILES macro/TPCFastTransformInit.C DESTINATION share/macro/) + + install(FILES macro/TPCFastTransformInitCPM.C + DESTINATION share/macro/) endif() if(ALIGPU_BUILD_TYPE STREQUAL "Standalone") diff --git a/GPU/TPCFastTransformation/macro/TPCFastTransformInitCPM.C b/GPU/TPCFastTransformation/macro/TPCFastTransformInitCPM.C new file mode 100644 index 0000000000000..a83efef0cdd75 --- /dev/null +++ b/GPU/TPCFastTransformation/macro/TPCFastTransformInitCPM.C @@ -0,0 +1,740 @@ +// Copyright 2019-2023 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file TPCFastTransformInitCPM.C +/// \brief A macro for generating TPC fast transformation +/// out of set of space charge correction voxels +/// +/// \author Sergey Gorbunov +/// + +/// how to run the macro: +/// +/// root -l TPCFastTransformInitCPM.C'("debugVoxRes.root")' +/// + +#if !defined(__CLING__) || defined(__ROOTCLING__) + +#include +#include +#include +#include +#include +#include "TFile.h" +#include "TSystem.h" +#include "TTree.h" +#include "TNtuple.h" +#include "TMath.h" +#include "Riostream.h" + +#include "CommonUtils/TreeStreamRedirector.h" +#include "MathUtils/fit.h" +#include "Algorithm/RangeTokenizer.h" +#include "Framework/Logger.h" +#include "GPU/TPCFastTransform.h" +#include "SpacePoints/TrackResiduals.h" +#include "TPCReconstruction/TPCFastTransformHelperO2.h" +#include "TPCCalibration/TPCFastSpaceChargeCorrectionHelper.h" + +#endif + +struct sMean { + float mean; + float rms; + float meanErr; + float rmsErr; + + ClassDef(sMean, 1); +}; + +#pragma link C++ class sMean + ; +#pragma link C++ class std::vector < sMean> + ; + +using namespace o2::tpc; +using namespace o2::gpu; +namespace mu = o2::math_utils; + +void createFastTransform(std::string outFileName, TTree* voxResTree, o2::tpc::TrackResiduals& trackResiduals, bool useSmoothed, bool invertSigns, float meanIDC = 0.f, float meanCTP = 0.f, int debug = 0, int useCTPLumi = 0); + +void TPCFastTransformInitCPM(const char* fileName = "debugVoxRes.root", + const char* outFileName = "TPCFastTransform_VoxRes.root", + bool useSmoothed = false, + int useCTPLumi = 0, + bool invertSigns = true, + float meanIDC = 0.f, + float meanCTP = 0.f, + int debug = 0, + int nThreads = 8) +{ + + // Initialise TPCFastTransform object from "voxRes" tree of + // o2::tpc::TrackResiduals::VoxRes track residual voxels + // + + /* + To visiualise the results: + + root -l transformDebug.root + corr->Draw("cx:y:z","iRoc==0&&iRow==10","") + grid->Draw("cx:y:z","iRoc==0&&iRow==10","same") + vox->Draw("vx:y:z","iRoc==0&&iRow==10","same") + */ + + o2::tpc::TPCFastSpaceChargeCorrectionHelper::instance()->setNthreads(nThreads); + + if (gSystem->AccessPathName(fileName)) { + LOGP(error, "input file {} does not exist!", fileName); + return; + } + + auto file = std::unique_ptr(TFile::Open(fileName, "READ")); + if (!file || !file->IsOpen()) { + LOGP(error, "could not open input file {}", fileName); + return; + } + + TTree* voxResTree = nullptr; + file->cd(); + gDirectory->GetObject("voxResTree", voxResTree); + if (!voxResTree) { + LOGP(error, "tree voxResTree does not exist!"); + return; + } + auto userInfo = voxResTree->GetUserInfo(); + if (!userInfo->FindObject("y2xBinning") || !userInfo->FindObject("z2xBinning")) { + LOGP(error, "'y2xBinning' or 'z2xBinning' not found in UserInfo, but required to get the correct binning"); + return; + } + + // Obtain configuration + const SpacePointsCalibConfParam& params = SpacePointsCalibConfParam::Instance(); + if (!std::filesystem::exists("scdconfig.ini")) { + LOGP(warning, "Did not find configuration file. Using default parameters and storing them in scdconfig.ini"); + params.writeINI("scdconfig.ini", "scdcalib"); // to write default parameters to a file + } else { + params.updateFromFile("scdconfig.ini"); + } + // TrackResiduals::setZ2XBinning() (called below) reads scdcalib.maxZ2X directly and uses it to scale + // the physical z/x bin boundaries -- baked into what each z2x voxel index in the input tree actually + // means, not a cosmetic knob. There is no scdconfig.ini on the GRID, so without this the code default + // (1.0) would silently apply instead of whatever stage 1 actually used (production 1.4), misaligning + // this macro's re-derived binning against the tree's real geometry. Must happen BEFORE setZ2XBinning() + // below. See staticMapCreatorCPM.C's UserInfo::Add("maxZ2X", ...) for where this comes from -- + // SmoothingExtrapolate.C clones the whole input UserInfo onto its own output, so it survives into this + // macro's input unchanged. + if (auto* maxZ2XObj = userInfo->FindObject("maxZ2X")) { + const std::string maxZ2XStr = maxZ2XObj->GetTitle(); + o2::conf::ConfigurableParam::setValue("scdcalib.maxZ2X", maxZ2XStr); + LOGP(info, "Set scdcalib.maxZ2X = {} from input UserInfo (matches stage 1)", maxZ2XStr); + } else { + LOGP(warning, + "'maxZ2X' not found in input UserInfo (older input file?) -- using scdcalib.maxZ2X = {} " + "(scdconfig.ini/code default), which may NOT match the value stage 1 actually used to " + "build this tree's z2x binning!", + params.maxZ2X); + } + + LOGP(info, "----- Dumping configuration values START -----"); + params.printKeyValues(); + LOGP(info, "----- Dumping configuration values END -----"); + + // required for the binning that was used + o2::tpc::TrackResiduals trackResiduals; + auto y2xBins = o2::RangeTokenizer::tokenize(userInfo->FindObject("y2xBinning")->GetTitle()); + const std::string z2xStr = userInfo->FindObject("z2xBinning")->GetTitle(); + auto z2xBins = o2::RangeTokenizer::tokenize(z2xStr); + LOGP(info, "z2xBins: {}", z2xStr); + trackResiduals.setY2XBinning(y2xBins); + trackResiduals.setZ2XBinning(z2xBins); + trackResiduals.init(); + + auto getFromUserInfo = [userInfo](std::string value, float& valueF) { + if (valueF != 0) { + LOGP(info, "{} set to {} via command line, not reading it from userInfo", value, valueF); + return; + } + + if (!userInfo || !userInfo->FindObject(value.data())) { + LOGP(error, "Could not find value for {} in userInfo", value); + valueF = 0.f; + return; + } + valueF = std::atof(userInfo->FindObject(value.data())->GetTitle()); + LOGP(info, "Found {} = {} in userInfo", value, valueF); + }; + + getFromUserInfo("meanIDC", meanIDC); + getFromUserInfo("meanCTP", meanCTP); + + if ((useCTPLumi != 2 && meanIDC == 0) || meanCTP == 0) { + LOGP(fatal, "meanCTP ({}) or meanIDC ({}) not set!", meanCTP, meanIDC); + } + if (useCTPLumi == 2) { + LOGP(warning, "Explicitly disabled IDCs!"); + } + + createFastTransform(outFileName, voxResTree, trackResiduals, useSmoothed, invertSigns, meanIDC, meanCTP, debug, useCTPLumi); +} + +void createFastTransform(std::string outFileName, TTree* voxResTree, o2::tpc::TrackResiduals& trackResiduals, bool useSmoothed, bool invertSigns, float meanIDC, float meanCTP, int debug, int useCTPLumi) +{ + LOGP(info, "create fast transformation ... "); + std::regex reg(".*FT_voxRes\\.residuals\\.([0-9]{6})_([0-9]{13})_([0-9]{13})_([0-9]+)_([0-9]+).*\\.root"); + std::smatch base_match; + int run = -1; + long validFrom = -1; + long validUntil = -1; + int firstTF = -1; + int lastTF = -1; + if (std::regex_match(outFileName, base_match, reg)) { + run = std::stoi(base_match[1].str()); + validFrom = std::stol(base_match[2].str()); + validUntil = std::stol(base_match[3].str()); + firstTF = std::stol(base_match[4].str()); + lastTF = std::stol(base_match[5].str()); + LOGP(info, "Found run {}, validFrom {}, validUntil {}, firstTF {}, lastTF {}", run, validFrom, validUntil, firstTF, lastTF); + } + + auto* helper = o2::tpc::TPCFastTransformHelperO2::instance(); + + o2::tpc::TPCFastSpaceChargeCorrectionHelper* corrHelper = o2::tpc::TPCFastSpaceChargeCorrectionHelper::instance(); + +#if __has_include("TPCFastTransformPOD.h") + TTree* voxResTreeInverse = nullptr; + o2::gpu::TPCFastSpaceChargeCorrectionMap mapDirect(0, 0), mapInverse(0, 0); + auto corrPtr = corrHelper->createFromTrackResiduals(trackResiduals, voxResTree, voxResTreeInverse, useSmoothed, invertSigns, &mapDirect, &mapInverse); +#else + auto corrPtr = corrHelper->createFromTrackResiduals(trackResiduals, voxResTree, useSmoothed, invertSigns); +#endif + + std::unique_ptr fastTransform(helper->create(0, *corrPtr)); + fastTransform->setLumi(meanCTP); + fastTransform->setIDC(meanIDC); // for SW version with IDC in FastTransfrom + + o2::gpu::TPCFastSpaceChargeCorrection& corr = fastTransform->getCorrection(); + + LOGP(info, "... create fast transformation completed"); + + if (!outFileName.empty()) { + fastTransform->writeToFile(outFileName.data(), "ccdb_object"); + } + + LOGP(info, "verify the results ..."); + + // the difference + + double maxDiff[3] = {0., 0., 0.}; + int maxDiffRoc[3] = {0, 0, 0}; + int maxDiffRow[3] = {0, 0, 0}; + + double sumDiff[3] = {0., 0., 0.}; + long nDiff = 0; + + // a debug file with some NTuples + + TDirectory* currDir = gDirectory; + + const std::filesystem::path pFileOutput(outFileName); + std::string outPath(pFileOutput.parent_path().c_str()); + if (outPath.empty()) { + outPath = "."; + } + const std::string fileOutputDebug = fmt::format("{}/{}.debug.root", outPath, pFileOutput.stem().c_str()); + const std::string fileOutputSummary = fmt::format("{}/{}.summary.root", outPath, pFileOutput.stem().c_str()); + + o2::utils::TreeStreamRedirector summary(fileOutputSummary.data(), "recreate"); + + TFile* debugFile = new TFile(fileOutputDebug.data(), "RECREATE"); + debugFile->cd(); + + // ntuple with the input data: voxel corrections + debugFile->cd(); + TNtuple* debugVox = new TNtuple("vox", "vox", "iRoc:iRow:y2xbin:z2xbin:x:y:z:vx:vy:vz:cx:cy:cz"); + + debugVox->SetMarkerStyle(8); + debugVox->SetMarkerSize(0.8); + debugVox->SetMarkerColor(kBlue); + + currDir->cd(); + + // check the difference in voxels and fill corresp. debug ntuple + + LOGP(info, "verify the results ..."); + + const o2::gpu::TPCFastTransformGeo& geo = helper->getGeometry(); + + o2::tpc::TrackResiduals::VoxRes* v = nullptr; + TBranch* branch = voxResTree->GetBranch("voxRes"); + branch->SetAddress(&v); + branch->SetAutoDelete(kTRUE); + + int nNaNdXV = 0; + int nNaNdYV = 0; + int nNaNdZV = 0; + int nNaNdXC = 0; + int nNaNdYC = 0; + int nNaNdZC = 0; + + int lastSector = -1; + + std::vector statsPerSecMean(36); + std::vector statsPerSecStdDev(36); + std::vector statsPerSecMedian(36); + + std::vector maxDiffPerSec[3]; + + std::vector deviationPerSecLTM95[3]; + std::vector deviationPerSecMedian[3]; + + std::vector entriesStats; + std::vector deviations[3]; + entriesStats.reserve(152 * trackResiduals.getNY2XBins() * trackResiduals.getNZ2XBins()); + for (int i = 0; i < 3; ++i) { + deviations[i].reserve(152 * trackResiduals.getNY2XBins() * trackResiduals.getNY2XBins()); + maxDiffPerSec[i].resize(36); + deviationPerSecLTM95[i].resize(36); + deviationPerSecMedian[i].resize(36); + } + + // retrieve infos from UserInfo + auto getFromUserInfo = [](TList* u, const char* name, int defVal = -1) { + const auto o = u->FindObject(name); + return o ? std::atoi(o->GetTitle()) : defVal; + }; + + auto userInfo = voxResTree->GetUserInfo(); + const int nSlicesPhiZ = getFromUserInfo(userInfo, "nSlicesPhiZ"); + const int maxTracks = getFromUserInfo(userInfo, "maxTracks"); + const int minTracks = getFromUserInfo(userInfo, "minTracks"); + const int nTracksProcessed = getFromUserInfo(userInfo, "nTracksProcessed"); + const bool badCalib = static_cast(getFromUserInfo(userInfo, "badCalib", 0)); + + for (int iVox = 0; iVox < voxResTree->GetEntriesFast(); iVox++) { + + voxResTree->GetEntry(iVox); + + const float voxEntries = v->stat[o2::tpc::TrackResiduals::VoxV]; + const int xBin = v->bvox[o2::tpc::TrackResiduals::VoxX]; // bin number in x (= pad row) + const int y2xBin = v->bvox[o2::tpc::TrackResiduals::VoxF]; // bin number in y/x 0..14 + const int z2xBin = v->bvox[o2::tpc::TrackResiduals::VoxZ]; // bin number in z/x 0..4 + const int iRoc = (int)v->bsec; + const int iRow = (int)xBin; + + const float x = trackResiduals.getX(xBin); // radius of the pad row + const float y2x = trackResiduals.getY2X(xBin, y2xBin); // y/x coordinate of the bin ~-0.15 ... 0.15 + const float z2x = trackResiduals.getZ2X(z2xBin); // z/x coordinate of the bin 0.1 .. 0.9 + const float y = x * y2x; +#if __has_include("TPCFastTransformPOD.h") + const float z = x * z2x * ((iRoc >= geo.getNumberOfSectorsA()) ? -1.f : 1.f); +#else + const float z = x * z2x * ((iRoc >= geo.getNumberOfSlicesA()) ? -1.f : 1.f); +#endif + + float correctionX = useSmoothed ? v->DS[o2::tpc::TrackResiduals::ResX] : v->D[o2::tpc::TrackResiduals::ResX]; + float correctionY = useSmoothed ? v->DS[o2::tpc::TrackResiduals::ResY] : v->D[o2::tpc::TrackResiduals::ResY]; + float correctionZ = useSmoothed ? v->DS[o2::tpc::TrackResiduals::ResZ] : v->D[o2::tpc::TrackResiduals::ResZ]; + + if (invertSigns) { + correctionX *= -1.; + correctionY *= -1.; + correctionZ *= -1.; + } + + entriesStats.emplace_back(voxEntries); + statsPerSecMean[iRoc] += voxEntries; + statsPerSecStdDev[iRoc] += voxEntries * voxEntries; + + nNaNdXV += TMath::IsNaN(correctionX); + nNaNdYV += TMath::IsNaN(correctionY); + nNaNdZV += TMath::IsNaN(correctionZ); + +#if __has_include("TPCFastTransformPOD.h") + float cx, cy, cz; + corr.getCorrectionLocal(iRoc, iRow, y, z, cx, cy, cz); +#else + float u, v, cx, cu, cv, cy, cz; + geo.convLocalToUV(iRoc, y, z, u, v); + corr.getCorrection(iRoc, iRow, u, v, cx, cu, cv); + geo.convUVtoLocal(iRoc, u + cu, v + cv, cy, cz); + cy -= y; + cz -= z; +#endif + + nNaNdXC += TMath::IsNaN(cx); + nNaNdYC += TMath::IsNaN(cy); + nNaNdZC += TMath::IsNaN(cz); + + const float d[3] = {cx - correctionX, cy - correctionY, cz - correctionZ}; + for (int i = 0; i < 3; i++) { + const float dAbs = std::abs(d[i]); + maxDiffPerSec[i][iRoc] = std::max(maxDiffPerSec[i][iRoc], dAbs); + deviations[i].emplace_back(dAbs); + if (std::abs(maxDiff[i]) < dAbs) { + maxDiff[i] = d[i]; + maxDiffRoc[i] = iRoc; + maxDiffRow[i] = iRow; + LOGP(info, "roc {} row {} xyz {} diff {}", iRoc, iRow, i, d[i]); + } + sumDiff[i] += d[i] * d[i]; + } + nDiff++; + + debugVox->Fill(iRoc, iRow, y2xBin, z2xBin, x, y, z, correctionX, correctionY, correctionZ, cx, cy, cz); + + if (lastSector > -1 && lastSector != iRoc) { + if (entriesStats.size() > 0) { + statsPerSecMean[lastSector] /= entriesStats.size(); + statsPerSecStdDev[lastSector] /= entriesStats.size(); + statsPerSecStdDev[lastSector] = (std::sqrt(std::abs(statsPerSecStdDev[lastSector] - statsPerSecMean[lastSector] * statsPerSecMean[lastSector]))); + statsPerSecMedian[lastSector] = mu::median(entriesStats); + } + entriesStats.clear(); + + static std::vector indexDev; + indexDev.resize(deviations[0].size()); + for (int i = 0; i < 3; i++) { + std::array fitRes; + mu::LTMUnbinned(deviations[i], indexDev, fitRes, 0.95); + deviationPerSecLTM95[i][lastSector].mean = fitRes[1]; + deviationPerSecLTM95[i][lastSector].rms = fitRes[2]; + deviationPerSecLTM95[i][lastSector].meanErr = fitRes[3]; + deviationPerSecLTM95[i][lastSector].rmsErr = fitRes[4]; + deviationPerSecMedian[i][lastSector] = mu::median(deviations[i]); + deviations[i].clear(); + } + } + lastSector = iRoc; + } + // last sector + if (lastSector > -1 && entriesStats.size() > 0) { + statsPerSecMean[lastSector] /= entriesStats.size(); + statsPerSecStdDev[lastSector] /= entriesStats.size(); + statsPerSecStdDev[lastSector] /= std::sqrt(std::abs(statsPerSecStdDev[lastSector] - statsPerSecMean[lastSector] * statsPerSecMean[lastSector])); + statsPerSecMedian[lastSector] = mu::median(entriesStats); + entriesStats.clear(); + + std::vector indexDev; + indexDev.resize(deviations[0].size()); + for (int i = 0; i < 3; i++) { + std::array fitRes; + mu::LTMUnbinned(deviations[i], indexDev, fitRes, 0.95); + deviationPerSecLTM95[i][lastSector].mean = fitRes[1]; + deviationPerSecLTM95[i][lastSector].rms = fitRes[2]; + deviationPerSecLTM95[i][lastSector].meanErr = fitRes[3]; + deviationPerSecLTM95[i][lastSector].rmsErr = fitRes[4]; + deviationPerSecMedian[i][lastSector] = mu::median(deviations[i]); + deviations[i].clear(); + } + } + + const int nNaNV = nNaNdXV + nNaNdYV + nNaNdZV; + const int nNaNC = nNaNdXC + nNaNdYC + nNaNdZC; + const auto sNaNV = fmt::format("NaNV: {} {} {} {}", nNaNV, nNaNdXV, nNaNdYV, nNaNdZV); + const auto sNaNC = fmt::format("NaNC: {} {} {} {}", nNaNC, nNaNdXC, nNaNdYC, nNaNdZC); + + if (nNaNV > 0) { + LOGP(error, "{}", sNaNV); + } else { + LOGP(info, "{}", sNaNV); + } + if (nNaNC > 0) { + LOGP(error, "{}", sNaNC); + } else { + LOGP(info, "{}", sNaNC); + } + + summary << "summary" + << "file=" << outFileName + // + << "meanIDC=" << meanIDC + << "meanCTP=" << meanCTP + << "run=" << run + << "validFrom=" << validFrom + << "validUntil=" << validUntil + << "firstTF=" << firstTF + << "lastTF =" << lastTF + // + << "nNaNdXV=" << nNaNdXV + << "nNaNdYV=" << nNaNdYV + << "nNaNdZV=" << nNaNdZV + << "nNaNdXC=" << nNaNdXC + << "nNaNdYC=" << nNaNdYC + << "nNaNdZC=" << nNaNdZC + // + << "statsMean=" << statsPerSecMean + << "statsStdDev=" << statsPerSecStdDev + << "statsMedian=" << statsPerSecMedian + // + << "DdXLTM95=" << deviationPerSecLTM95[0] + << "DdYLTM95=" << deviationPerSecLTM95[1] + << "DdZLTM95=" << deviationPerSecLTM95[2] + << "DdXMedian=" << deviationPerSecMedian[0] + << "DdYMedian=" << deviationPerSecMedian[1] + << "DdZMedian=" << deviationPerSecMedian[2] + << "DdXMax=" << maxDiffPerSec[0] + << "DdYMax=" << maxDiffPerSec[1] + << "DdZMax=" << maxDiffPerSec[2] + // + << "nSlicesPhiZ=" << nSlicesPhiZ + << "maxTracks=" << maxTracks + << "minTracks=" << minTracks + << "nTracksProcessed=" << nTracksProcessed + << "badCalib=" << badCalib + << "\n"; + + summary.Close(); + +#if __has_include("TPCFastTransformPOD.h") + if (debug > 0) { + debugFile->cd(); + TNtuple* ntAll = new TNtuple("all", "all", "sec:row:x:y:z:cx:cy:cz:ix:iy:iz"); + ntAll->SetMarkerStyle(8); + ntAll->SetMarkerSize(0.1); + ntAll->SetMarkerColor(kBlack); + + debugFile->cd(); + TNtuple* ntGrid = new TNtuple("grid", "grid", "sec:row:x:y:z:cx:cy:cz:ix:iy:iz"); + ntGrid->SetMarkerStyle(8); + ntGrid->SetMarkerSize(1.2); + ntGrid->SetMarkerColor(kBlack); + + debugFile->cd(); + TNtuple* ntFitPoints = new TNtuple("fitpoints", "fit points", "sec:row:x:y:z:px:py:pz:cx:cy:cz"); + ntFitPoints->SetMarkerStyle(8); + ntFitPoints->SetMarkerSize(0.4); + ntFitPoints->SetMarkerColor(kRed); + + currDir->cd(); + + auto getInvCorrections = [&](int iSector, int iRow, float realY, float realZ, float& ix, float& iy, float& iz) { + ix = corr.getCorrectionXatRealYZ(iSector, iRow, realY, realZ); + corr.getCorrectionYZatRealYZ(iSector, iRow, realY, realZ, iy, iz); + }; + + auto getAllCorrections = [&](int iSector, int iRow, float y, float z, float& cx, float& cy, float& cz, float& ix, float& iy, float& iz) { + corr.getCorrectionLocal(iSector, iRow, y, z, cx, cy, cz); + getInvCorrections(iSector, iRow, y + cy, z + cz, ix, iy, iz); + }; + + LOGP(info, "create debug ntuples at spline grid points and high granular ..."); + + for (int32_t iSector = 0; iSector < geo.getNumberOfSectors(); iSector++) { + LOGP(info, "debug ntuples for sector {}", iSector); + + for (int32_t iRow = 0; iRow < geo.getNumberOfRows(); iRow++) { + + double x = geo.getRowInfo(iRow).x; + + const auto& gridY = corr.getSplineForRow(iRow).getGridX1(); + const auto& gridZ = corr.getSplineForRow(iRow).getGridX2(); + + { + std::vector points[2], knots[2]; + auto [yMin, yMax] = geo.getRowInfo(iRow).getYrange(); + auto [zMin, zMax] = geo.getZrange(iSector); + + for (int32_t iu = 0; iu < gridY.getNumberOfKnots(); iu++) { + float y, z; + corr.convGridToLocal(iSector, iRow, gridY.getKnot(iu).getU(), 0., y, z); + knots[0].push_back(y); + points[0].push_back(y); + } + for (int32_t iv = 0; iv < gridZ.getNumberOfKnots(); iv++) { + float y, z; + corr.convGridToLocal(iSector, iRow, 0., gridZ.getKnot(iv).getU(), y, z); + knots[1].push_back(z); + points[1].push_back(z); + } + + for (int32_t iyz = 0; iyz <= 1; iyz++) { + std::sort(knots[iyz].begin(), knots[iyz].end()); + std::sort(points[iyz].begin(), points[iyz].end()); + int32_t n = points[iyz].size(); + int nsteps = (iyz == 0) ? 10 : 5; + for (int32_t i = 0; i < n - 1; i++) { + double d = (points[iyz][i + 1] - points[iyz][i]) / nsteps; + for (int32_t ii = 1; ii < nsteps; ii++) { + points[iyz].push_back(points[iyz][i] + d * ii); + } + } + } + points[0].push_back(yMin); + points[0].push_back(yMax); + points[1].push_back(zMin); + points[1].push_back(zMax); + for (int32_t iyz = 0; iyz <= 1; iyz++) { + std::sort(points[iyz].begin(), points[iyz].end()); + } + + for (int32_t iter = 0; iter < 2; iter++) { + std::vector& py = ((iter == 0) ? knots[0] : points[0]); + std::vector& pz = ((iter == 0) ? knots[1] : points[1]); + for (uint32_t iu = 0; iu < py.size(); iu++) { + for (uint32_t iv = 0; iv < pz.size(); iv++) { + float y = py[iu]; + float z = pz[iv]; + float cx{0}, cy{0}, cz{0}, ix{0}, iy{0}, iz{0}; + getAllCorrections(iSector, iRow, y, z, cx, cy, cz, ix, iy, iz); + if (iter == 0) { + ntGrid->Fill(iSector, iRow, x, y, z, cx, cy, cz, ix, iy, iz); + } else { + ntAll->Fill(iSector, iRow, x, y, z, cx, cy, cz, ix, iy, iz); + } + } + } + } + } + + // the data points used in spline fit + auto& fitPoints = mapDirect.getPoints(iSector, iRow); + for (uint32_t ip = 0; ip < fitPoints.size(); ip++) { + auto point = fitPoints[ip]; + float y = point.mY; + float z = point.mZ; + float correctionX = point.mDx; + float correctionY = point.mDy; + float correctionZ = point.mDz; + float cx, cy, cz; + corr.getCorrectionLocal(iSector, iRow, y, z, cx, cy, cz); + ntFitPoints->Fill(iSector, iRow, x, y, z, correctionX, correctionY, correctionZ, cx, cy, cz); + } + } + } + + debugFile->cd(); + ntAll->Write(); + ntGrid->Write(); + ntFitPoints->Write(); + } +#else + if (debug > 0) { + // ntuple with spline grid points + debugFile->cd(); + // ntuple with created TPC corrections + TNtuple* debugCorr = new TNtuple("corr", "corr", "iRoc:iRow:x:y:z:cx:cy:cz"); + + debugCorr->SetMarkerStyle(8); + debugCorr->SetMarkerSize(0.1); + debugCorr->SetMarkerColor(kBlack); + + TNtuple* debugGrid = new TNtuple("grid", "grid", "iRoc:iRow:x:y:z:cx:cy:cz"); + + debugGrid->SetMarkerStyle(8); + debugGrid->SetMarkerSize(1.2); + debugGrid->SetMarkerColor(kBlack); + + // ntuple with data points created from voxels (with data smearing and + // extension to the edges) + TNtuple* debugPoints = new TNtuple("points", "points", "iRoc:iRow:x:y:z:px:py:pz:cx:cy:cz"); + + debugPoints->SetMarkerStyle(8); + debugPoints->SetMarkerSize(0.4); + debugPoints->SetMarkerColor(kRed); + + currDir->cd(); + + LOGP(info, "create debug ntuples at spline grid points and high granular ..."); + + for (int iRoc = 0; iRoc < geo.getNumberOfSlices(); iRoc++) { + LOGP(info, "debug ntuples for roc {}", iRoc); + for (int iRow = 0; iRow < geo.getNumberOfRows(); iRow++) { + + double x = geo.getRowInfo(iRow).x; + + // the correction + + for (double su = 0.; su <= 1.0001; su += 0.01) { + for (double sv = 0.; sv <= 1.0001; sv += 0.1) { + float u, v; + geo.convScaledUVtoUV(iRoc, iRow, su, sv, u, v); + float y, z; + geo.convUVtoLocal(iRoc, u, v, y, z); + float cx, cu, cv; + corr.getCorrection(iRoc, iRow, u, v, cx, cu, cv); + float cy, cz; + geo.convUVtoLocal(iRoc, u + cu, v + cv, cy, cz); + cy -= y; + cz -= z; + debugCorr->Fill(iRoc, iRow, x, y, z, cx, cy, cz); + } + } + + // the spline grid + + const auto& gridU = corr.getSpline(iRoc, iRow).getGridX1(); + const auto& gridV = corr.getSpline(iRoc, iRow).getGridX2(); + for (int iu = 0; iu < gridU.getNumberOfKnots(); iu++) { + // double su = gridU.convUtoX(gridU.getKnot(iu).getU()); + for (int iv = 0; iv < gridV.getNumberOfKnots(); iv++) { + // double sv = gridV.convUtoX(gridV.getKnot(iv).getU()); + float u, v; + corr.convGridToUV(iRoc, iRow, iu, iv, u, v); + float y, z; + geo.convUVtoLocal(iRoc, u, v, y, z); + float cx, cu, cv; + corr.getCorrection(iRoc, iRow, u, v, cx, cu, cv); + float cy, cz; + geo.convUVtoLocal(iRoc, u + cu, v + cv, cy, cz); + cy -= y; + cz -= z; + debugGrid->Fill(iRoc, iRow, x, y, z, cx, cy, cz); + } + } + + // the data points used in spline fit + // (they are kept in + // TPCFastTransformHelperO2::instance()->getCorrectionMap() ) + + o2::gpu::TPCFastSpaceChargeCorrectionMap& map = corrHelper->getCorrectionMap(); + auto& points = map.getPoints(iRoc, iRow); + + for (unsigned int ip = 0; ip < points.size(); ip++) { + auto point = points[ip]; + float y = point.mY; + float z = point.mZ; + float correctionX = point.mDx; + float correctionY = point.mDy; + float correctionZ = point.mDz; + + float u, v, cx, cu, cv, cy, cz; + geo.convLocalToUV(iRoc, y, z, u, v); + corr.getCorrection(iRoc, iRow, u, v, cx, cu, cv); + geo.convUVtoLocal(iRoc, u + cu, v + cv, cy, cz); + cy -= y; + cz -= z; + + debugPoints->Fill(iRoc, iRow, x, y, z, correctionX, correctionY, correctionZ, cx, cy, cz); + } + } + } + + debugFile->cd(); + debugCorr->Write(); + debugGrid->Write(); + debugPoints->Write(); + } +#endif + + for (int i = 0; i < 3; i++) { + sumDiff[i] = sqrt(sumDiff[i]) / nDiff; + } + + LOGP(info, "Max difference in x : {} at ROC {} row {}", maxDiff[0], maxDiffRoc[0], maxDiffRow[0]); + LOGP(info, "Max difference in y : {} at ROC {} row {}", maxDiff[1], maxDiffRoc[1], maxDiffRow[1]); + LOGP(info, "Max difference in z : {} at ROC {} row {}", maxDiff[2], maxDiffRoc[2], maxDiffRow[2]); + LOGP(info, "Mean difference in x,y,z : {} {} {}", sumDiff[0], sumDiff[1], sumDiff[2]); + + corr.testInverse(0); + + debugFile->cd(); + debugVox->Write(); + debugFile->Close(); +} diff --git a/GPU/Workflow/include/GPUWorkflow/GPUWorkflowSpec.h b/GPU/Workflow/include/GPUWorkflow/GPUWorkflowSpec.h index 7b6bffcac9e1e..978c5f312cfe7 100644 --- a/GPU/Workflow/include/GPUWorkflow/GPUWorkflowSpec.h +++ b/GPU/Workflow/include/GPUWorkflow/GPUWorkflowSpec.h @@ -162,6 +162,8 @@ class GPURecoWorkflowSpec : public o2::framework::Task aligned_unique_buffer_ptr mFastTransformBuffer; }; + void storeConfigs(o2::framework::ProcessingContext& pc); + /// initialize TPC options from command line void initFunctionTPCCalib(o2::framework::InitContext& ic); void initFunctionITS(o2::framework::InitContext& ic); diff --git a/GPU/Workflow/src/GPUWorkflowITS.cxx b/GPU/Workflow/src/GPUWorkflowITS.cxx index ac9834d3eacd1..2a0e36d65bb7a 100644 --- a/GPU/Workflow/src/GPUWorkflowITS.cxx +++ b/GPU/Workflow/src/GPUWorkflowITS.cxx @@ -23,6 +23,8 @@ #include "CommonUtils/NameConf.h" #include "ITStracking/TrackingInterface.h" #include "ITStracking/TrackingConfigParam.h" +#include +#include #ifdef ENABLE_UPGRADES #include "ITS3Reconstruction/TrackingInterface.h" @@ -36,11 +38,6 @@ int32_t GPURecoWorkflowSpec::runITSTracking(o2::framework::ProcessingContext& pc mITSTimeFrame->setDevicePropagator(mGPUReco->GetDeviceO2Propagator()); LOGP(debug, "GPUChainITS is giving me device propagator: {}", (void*)mGPUReco->GetDeviceO2Propagator()); mITSTrackingInterface->run(pc); - static bool first = true; - if (mNTFs == 1 && pc.services().get().inputTimesliceId == 0) { - o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, o2::its::VertexerParamConfig::Instance().getName()), o2::its::VertexerParamConfig::Instance().getName()); - o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, o2::its::TrackerParamConfig::Instance().getName()), o2::its::TrackerParamConfig::Instance().getName()); - } return 0; } diff --git a/GPU/Workflow/src/GPUWorkflowInternal.h b/GPU/Workflow/src/GPUWorkflowInternal.h index 1ad6f3df13f5a..aa6ab80af576b 100644 --- a/GPU/Workflow/src/GPUWorkflowInternal.h +++ b/GPU/Workflow/src/GPUWorkflowInternal.h @@ -64,11 +64,12 @@ struct GPURecoWorkflowSpec_PipelineInternals { fair::mq::Device* fmqDevice = nullptr; volatile fair::mq::State fmqState = fair::mq::State::Undefined, fmqPreviousState = fair::mq::State::Undefined; - volatile bool endOfStreamAsyncReceived = false; + volatile bool endOfStreamAsyncWaiting = true; volatile bool endOfStreamDplReceived = false; volatile bool runStarted = false; volatile bool shouldTerminate = false; - std::mutex stateMutex; + volatile bool pipelineAbort = false; + std::mutex stateMutex, receiveMutex; std::condition_variable stateNotify; std::thread receiveThread; diff --git a/GPU/Workflow/src/GPUWorkflowPipeline.cxx b/GPU/Workflow/src/GPUWorkflowPipeline.cxx index f0aeb8089e27a..305772331abb3 100644 --- a/GPU/Workflow/src/GPUWorkflowPipeline.cxx +++ b/GPU/Workflow/src/GPUWorkflowPipeline.cxx @@ -116,11 +116,15 @@ void GPURecoWorkflowSpec::enqueuePipelinedJob(GPUTrackingInOutPointers* ptrs, GP context->jobInputUpdateCallback = std::make_unique(); if (!inputFinal) { - context->jobInputUpdateCallback->callback = [context](GPUTrackingInOutPointers*& data, GPUInterfaceOutputs*& outputs) { + context->jobInputUpdateCallback->callback = [context, this](GPUTrackingInOutPointers*& data, GPUInterfaceOutputs*& outputs) -> int32_t { std::unique_lock lk(context->jobInputFinalMutex); - context->jobInputFinalNotify.wait(lk, [context]() { return context->jobInputFinal; }); + context->jobInputFinalNotify.wait(lk, [context, this]() { return context->jobInputFinal || mPipeline->pipelineAbort; }); + if (mPipeline->pipelineAbort) { + return 1; + } data = context->jobPtrs; outputs = context->jobOutputRegions; + return 0; }; } context->jobInputUpdateCallback->notifyCallback = [this]() { @@ -195,15 +199,17 @@ int32_t GPURecoWorkflowSpec::handlePipeline(ProcessingContext& pc, GPUTrackingIn } size_t prepareBufferSize = sizeof(pipelinePrepareMessage) + ptrsTotal * sizeof(size_t) * 4; - std::vector messageBuffer(prepareBufferSize / sizeof(size_t)); - pipelinePrepareMessage& preMessage = *(pipelinePrepareMessage*)messageBuffer.data(); + fair::mq::MessagePtr payload(device->NewMessage()); + payload->Rebuild(prepareBufferSize, fair::mq::Alignment(sizeof(size_t))); + auto* messageBuffer = (size_t*)payload->GetData(); + pipelinePrepareMessage& preMessage = *(pipelinePrepareMessage*)messageBuffer; preMessage.magicWord = preMessage.MAGIC_WORD; preMessage.timeSliceId = tinfo.timeslice; preMessage.pointersTotal = ptrsTotal; preMessage.flagEndOfStream = false; memcpy((void*)&preMessage.tfSettings, (const void*)ptrs.settingsTF, sizeof(preMessage.tfSettings)); - size_t* ptrBuffer = messageBuffer.data() + sizeof(preMessage) / sizeof(size_t); + size_t* ptrBuffer = messageBuffer + sizeof(preMessage) / sizeof(size_t); size_t ptrsCopied = 0; int32_t lastRegion = -1; for (uint32_t i = 0; i < GPUTrackingInOutZS::NSECTORS; i++) { @@ -234,9 +240,7 @@ int32_t GPURecoWorkflowSpec::handlePipeline(ProcessingContext& pc, GPUTrackingIn } auto channel = device->GetChannels().find("gpu-prepare-channel"); - fair::mq::MessagePtr payload(device->NewMessage()); LOG(info) << "Sending gpu-reco-workflow prepare message of size " << prepareBufferSize; - payload->Rebuild(messageBuffer.data(), prepareBufferSize, nullptr, nullptr); channel->second[0].Send(payload); return 2; } @@ -251,12 +255,13 @@ void GPURecoWorkflowSpec::handlePipelineEndOfStream(EndOfStreamContext& ec) } if (mSpecConfig.enableDoublePipeline == 2) { auto* device = ec.services().get().device(); - pipelinePrepareMessage preMessage; - preMessage.flagEndOfStream = true; - auto channel = device->GetChannels().find("gpu-prepare-channel"); fair::mq::MessagePtr payload(device->NewMessage()); + payload->Rebuild(sizeof(pipelinePrepareMessage), fair::mq::Alignment(alignof(pipelinePrepareMessage))); + auto* preMessage = (pipelinePrepareMessage*)payload->GetData(); + new (preMessage) pipelinePrepareMessage; + preMessage->flagEndOfStream = true; + auto channel = device->GetChannels().find("gpu-prepare-channel"); LOG(info) << "Sending end-of-stream message over out-of-bands channel"; - payload->Rebuild(&preMessage, sizeof(preMessage), nullptr, nullptr); channel->second[0].Send(payload); } } @@ -264,7 +269,33 @@ void GPURecoWorkflowSpec::handlePipelineEndOfStream(EndOfStreamContext& ec) void GPURecoWorkflowSpec::handlePipelineStop() { if (mSpecConfig.enableDoublePipeline == 1) { - mPipeline->mayInjectTFId = 0; + { + std::unique_lock lk(mPipeline->queueMutex); + mPipeline->pipelineAbort = mPipeline->pipelineQueue.size(); + } + if (mPipeline->pipelineAbort) { + mPipeline->pipelineQueue.front()->jobInputFinalNotify.notify_one(); + mGPUReco->DrainPipeline(); + { + std::unique_lock lk(mPipeline->queueMutex); + mPipeline->pipelineQueue = {}; + } + { + std::lock_guard lk(mPipeline->completionPolicyMutex); + mPipeline->completionPolicyQueue = {}; + } + mPipeline->pipelineAbort = false; + { + std::lock_guard lk(mPipeline->stateMutex); + mPipeline->endOfStreamAsyncWaiting = false; + mPipeline->mNTFReceived = 0; + mPipeline->runStarted = false; + } + } + { + std::unique_lock lk(mPipeline->mayInjectMutex); + mPipeline->mayInjectTFId = 0; + } } } @@ -273,16 +304,19 @@ void GPURecoWorkflowSpec::receiveFMQStateCallback(fair::mq::State newState) { std::lock_guard lk(mPipeline->stateMutex); if (mPipeline->fmqState != fair::mq::State::Running && newState == fair::mq::State::Running) { - mPipeline->endOfStreamAsyncReceived = false; + mPipeline->endOfStreamAsyncWaiting = true; mPipeline->endOfStreamDplReceived = false; } mPipeline->fmqPreviousState = mPipeline->fmqState; mPipeline->fmqState = newState; + } + mPipeline->stateNotify.notify_all(); + { + std::lock_guard lk(mPipeline->receiveMutex); if (newState == fair::mq::State::Exiting) { mPipeline->fmqDevice->UnsubscribeFromStateChange(GPURecoWorkflowSpec_FMQCallbackKey); } } - mPipeline->stateNotify.notify_all(); } void GPURecoWorkflowSpec::RunReceiveThread() @@ -293,7 +327,7 @@ void GPURecoWorkflowSpec::RunReceiveThread() int32_t recvTimeot = 1000; fair::mq::MessagePtr msg; LOG(debug) << "Waiting for out of band message"; - auto shouldReceive = [this]() { return ((mPipeline->fmqState == fair::mq::State::Running || (mPipeline->fmqState == fair::mq::State::Ready && mPipeline->fmqPreviousState == fair::mq::State::Running)) && !mPipeline->endOfStreamAsyncReceived); }; + auto shouldReceive = [this]() { return ((mPipeline->fmqState == fair::mq::State::Running || (mPipeline->fmqState == fair::mq::State::Ready && mPipeline->fmqPreviousState == fair::mq::State::Running)) && mPipeline->endOfStreamAsyncWaiting); }; do { { std::unique_lock lk(mPipeline->stateMutex); @@ -304,7 +338,7 @@ void GPURecoWorkflowSpec::RunReceiveThread() } try { do { - std::unique_lock lk(mPipeline->stateMutex); + std::unique_lock lk(mPipeline->receiveMutex); if (!shouldReceive()) { break; } @@ -327,10 +361,13 @@ void GPURecoWorkflowSpec::RunReceiveThread() } if (m->flagEndOfStream) { LOG(info) << "Received end-of-stream from out-of-band channel"; - std::lock_guard lk(mPipeline->stateMutex); - mPipeline->endOfStreamAsyncReceived = true; - mPipeline->mNTFReceived = 0; - mPipeline->runStarted = false; + { + std::lock_guard lk(mPipeline->stateMutex); + mPipeline->endOfStreamAsyncWaiting = false; + mPipeline->mNTFReceived = 0; + mPipeline->runStarted = false; + } + mPipeline->stateNotify.notify_all(); continue; } @@ -342,7 +379,7 @@ void GPURecoWorkflowSpec::RunReceiveThread() { std::unique_lock lk(mPipeline->stateMutex); - mPipeline->stateNotify.wait(lk, [this]() { return (mPipeline->runStarted && !mPipeline->endOfStreamAsyncReceived) || mPipeline->shouldTerminate; }); + mPipeline->stateNotify.wait(lk, [this]() { return (mPipeline->runStarted && mPipeline->endOfStreamAsyncWaiting) || mPipeline->shouldTerminate; }); if (!mPipeline->runStarted) { continue; } diff --git a/GPU/Workflow/src/GPUWorkflowSpec.cxx b/GPU/Workflow/src/GPUWorkflowSpec.cxx index d928cfdd502c9..e51a45044da2b 100644 --- a/GPU/Workflow/src/GPUWorkflowSpec.cxx +++ b/GPU/Workflow/src/GPUWorkflowSpec.cxx @@ -14,6 +14,9 @@ /// @since 2018-04-18 /// @brief Processor spec for running TPC CA tracking +#include +#include +#include "GPUO2ConfigurableParam.h" #include "GPUWorkflow/GPUWorkflowSpec.h" #include "Headers/DataHeader.h" #include "Framework/WorkflowSpec.h" // o2::framework::mergeInputs @@ -75,6 +78,7 @@ #include "GPUReconstructionConvert.h" #include "DetectorsRaw/RDHUtils.h" #include "ITStracking/TrackingInterface.h" +#include "ITStracking/TrackingConfigParam.h" #include "GPUWorkflowInternal.h" #include "GPUDataTypesQA.h" // #include "Framework/ThreadPool.h" @@ -260,8 +264,8 @@ void GPURecoWorkflowSpec::init(InitContext& ic) if (mConfig->configCalib.matLUT == nullptr) { LOGF(fatal, "Error loading matlut file"); } - } else { - mConfig->configProcessing.lateO2MatLutProvisioningSize = 50 * 1024 * 1024; + } else if (mConfig->configProcessing.lateO2MatLutProvisioningSize <= 0) { + mConfig->configProcessing.lateO2MatLutProvisioningSize = 55 * 1024 * 1024; } if (mSpecConfig.readTRDtracklets) { @@ -767,6 +771,9 @@ void GPURecoWorkflowSpec::run(ProcessingContext& pc) } // ------------------------------ Actual processing ------------------------------ + if (mNTFs == 1 && pc.services().get().inputTimesliceId == 0) { + storeConfigs(pc); + } if ((int32_t)(ptrs.tpcZS != nullptr) + (int32_t)(ptrs.tpcPackedDigits != nullptr && (ptrs.tpcZS == nullptr || ptrs.tpcPackedDigits->tpcDigitsMC == nullptr)) + (int32_t)(ptrs.clustersNative != nullptr) + (int32_t)(ptrs.tpcCompressedClusters != nullptr) != 1) { throw std::runtime_error("Invalid input for gpu tracking"); @@ -805,9 +812,6 @@ void GPURecoWorkflowSpec::run(ProcessingContext& pc) mNTFDumps++; } } - if (mNTFs == 1 && pc.services().get().inputTimesliceId == 0) { // TPC ConfigurableCarams are somewhat special, need to construct by hand - o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, "rec_tpc"), "GPU_rec_tpc,GPU_rec,GPU_proc_param,GPU_proc,GPU_global,trackTuneParams"); - } std::unique_ptr ptrsDump; if (mConfParam->dumpBadTFMode == 2) { @@ -1020,6 +1024,29 @@ void GPURecoWorkflowSpec::run(ProcessingContext& pc) LOG(info) << "GPU Reconstruction time for this TF " << mTimer->CpuTime() - cput << " s (cpu), " << mTimer->RealTime() - realt << " s (wall)"; } +void GPURecoWorkflowSpec::storeConfigs(ProcessingContext& pc) +{ + // TPC ConfigurableCarams are somewhat special, need to construct by hand + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, "rec_tpc"), "GPU_rec_tpc,GPU_rec,GPU_proc_param,GPU_proc,GPU_global,trackTuneParams"); + TMap md; + md.SetOwnerKeyValue(); + md.Add(new TObjString(o2::gpu::internal::GPUConfigurableParamGPUSettingsRecTPC::Instance().getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(o2::gpu::internal::GPUConfigurableParamGPUSettingsRecTPC::Instance().getName()).c_str())); + md.Add(new TObjString(o2::gpu::internal::GPUConfigurableParamGPUSettingsRec::Instance().getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(o2::gpu::internal::GPUConfigurableParamGPUSettingsRec::Instance().getName()).c_str())); + md.Add(new TObjString(o2::gpu::internal::GPUConfigurableParamGPUSettingsProcessingParam::Instance().getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(o2::gpu::internal::GPUConfigurableParamGPUSettingsProcessingParam::Instance().getName()).c_str())); + md.Add(new TObjString(o2::gpu::internal::GPUConfigurableParamGPUSettingsProcessing::Instance().getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(o2::gpu::internal::GPUConfigurableParamGPUSettingsProcessing::Instance().getName()).c_str())); + md.Add(new TObjString(o2::gpu::internal::GPUConfigurableParamGPUSettingsO2::Instance().getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(o2::gpu::internal::GPUConfigurableParamGPUSettingsO2::Instance().getName()).c_str())); + md.Add(new TObjString(o2::globaltracking::TrackTuneParams::Instance().getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(o2::globaltracking::TrackTuneParams::Instance().getName()).c_str())); + if (mSpecConfig.runITSTracking) { + const auto& vtconf = o2::its::VertexerParamConfig::Instance(); + const auto& trconf = o2::its::TrackerParamConfig::Instance(); + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, vtconf.getName()), vtconf.getName()); + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, trconf.getName()), trconf.getName()); + md.Add(new TObjString(vtconf.getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(vtconf.getName()).c_str())); + md.Add(new TObjString(trconf.getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(trconf.getName()).c_str())); + } + pc.outputs().snapshot(Output{"META", "GPUTRACKER", 0}, md); +} + void GPURecoWorkflowSpec::doCalibUpdates(o2::framework::ProcessingContext& pc, calibObjectStruct& oldCalibObjects) { GPUCalibObjectsConst newCalibObjects; @@ -1368,6 +1395,7 @@ Outputs GPURecoWorkflowSpec::outputs() if (mSpecConfig.outputErrorQA) { outputSpecs.emplace_back(gDataOriginGPU, "ERRORQA", 0, Lifetime::Timeframe); } + outputSpecs.emplace_back("META", "GPUTRACKER", 0, Lifetime::Sporadic); if (mSpecConfig.runITSTracking) { outputSpecs.emplace_back(gDataOriginITS, "TRACKS", 0, Lifetime::Timeframe); diff --git a/Generators/include/Generators/GeneratorFromFile.h b/Generators/include/Generators/GeneratorFromFile.h index 9bf1d4911008b..ad93814467fc3 100644 --- a/Generators/include/Generators/GeneratorFromFile.h +++ b/Generators/include/Generators/GeneratorFromFile.h @@ -19,12 +19,12 @@ #include "Generators/GeneratorFromO2KineParam.h" #include "SimulationDataFormat/MCEventHeader.h" #include +#include #include class TBranch; class TFile; class TParticle; -class TGrid; namespace o2 { diff --git a/Generators/include/Generators/TPCLoopers.h b/Generators/include/Generators/TPCLoopers.h index a144a947fc11b..3e8685257b829 100644 --- a/Generators/include/Generators/TPCLoopers.h +++ b/Generators/include/Generators/TPCLoopers.h @@ -107,8 +107,16 @@ class GenTPCLoopers void SetAdjust(float adjust = 0.f); + void setGeomProtection(bool protect); + + // check if a vertex lies in the TPC volume where ionisation can be recorded + bool isInTPCActiveVolume(double vx, double vy) const; + unsigned int getNLoopers() const { return (mNLoopersPairs + mNLoopersCompton); } + // loopers dropped by the geometrical protection since the last reset + unsigned int getNSkipped() const { return mNSkippedPairs + mNSkippedCompton; } + private: std::unique_ptr mONNX_pair = nullptr; std::unique_ptr mONNX_compton = nullptr; @@ -137,6 +145,9 @@ class GenTPCLoopers double mTimeEnd = 0.0; // Time limit for the last event float mLoopsFractionPairs = 0.08; // Fraction of loopers from Pairs int mInteractionRate = 50000; // Interaction rate in Hz + bool mGeomProtection = true; // Skip loopers generated outside the TPC active volume + unsigned int mNSkippedPairs = 0; // Pairs dropped by the geometrical protection + unsigned int mNSkippedCompton = 0; // Compton electrons dropped by the geometrical protection }; #endif // GENERATORS_WITH_TPCLOOPERS diff --git a/Generators/include/Generators/TPCLoopersParam.h b/Generators/include/Generators/TPCLoopersParam.h index 87e4510d6e617..db75c1311d020 100644 --- a/Generators/include/Generators/TPCLoopersParam.h +++ b/Generators/include/Generators/TPCLoopersParam.h @@ -45,6 +45,7 @@ struct GenTPCLoopersParam : public o2::conf::ConfigurableParamHelpersetGeomProtection(loopersParam.geomProtection); const auto& intrate = loopersParam.intrate; // Configure the generator with flat gas loopers defined per orbit with clusters/track info // If intrate is negative (default), automatic IR from collisioncontext.root will be used @@ -260,6 +261,10 @@ Bool_t mParticles.insert(mParticles.end(), looperParticles.begin(), looperParticles.end()); LOG(debug) << "Added " << looperParticles.size() << " looper particles"; + const auto skippedLoopers = mTPCLoopersGen->getNSkipped(); + if (skippedLoopers > 0) { + LOG(debug) << "Geometrical protection skipped " << skippedLoopers << " loopers outside the TPC active volume"; + } } #endif return kTRUE; diff --git a/Generators/src/GeneratorHybrid.cxx b/Generators/src/GeneratorHybrid.cxx index fdca64beeb701..ed831aef8b16a 100644 --- a/Generators/src/GeneratorHybrid.cxx +++ b/Generators/src/GeneratorHybrid.cxx @@ -245,6 +245,18 @@ Bool_t GeneratorHybrid::Init() } count++; } + // Label for groups: concatenation of the names of the generators in the group, separated by '+'. + // Currently used only if randomisation is enabled + auto groupLabel = [this](int k) { + std::string label; + for (auto subIndex : mGroups[k]) { + if (!label.empty()) { + label += "+"; + } + label += (mConfigs[subIndex] == "" ? mGens[subIndex] : mConfigs[subIndex]); + } + return label; + }; if (mRandomize) { if (std::all_of(mFractions.begin(), mFractions.end(), [](int i) { return i == 1; })) { LOG(info) << "Full randomisation of generators order"; @@ -261,12 +273,12 @@ Bool_t GeneratorHybrid::Init() if (mFractions[k] == 0) { // Generator will not be used if fraction is 0 mRngFractions.push_back(-1); - LOG(info) << "Generator " << mGens[k] << " will not be used"; + LOG(info) << "Generator " << groupLabel(k) << " will not be used"; } else { chance = static_cast(mFractions[k]) / allfracs; sum += chance; mRngFractions.push_back(sum); - LOG(info) << "Generator " << (mConfigs[k] == "" ? mGens[k] : mConfigs[k]) << " has a " << chance * 100 << "% chance of being used"; + LOG(info) << "Generator " << groupLabel(k) << " has a " << chance * 100 << "% chance of being used"; } } } @@ -707,6 +719,10 @@ Bool_t GeneratorHybrid::parseJSON(const std::string& path) if (doc.HasMember("fractions")) { const auto& fractions = doc["fractions"]; for (const auto& frac : fractions.GetArray()) { + if (!frac.IsInt()) { + LOG(fatal) << "Fractions must be integers. Wrong type found in JSON"; + return false; + } mFractions.push_back(frac.GetInt()); } } else { diff --git a/Generators/src/TPCLoopers.cxx b/Generators/src/TPCLoopers.cxx index 41551ab483660..a587f1fc8d1c9 100644 --- a/Generators/src/TPCLoopers.cxx +++ b/Generators/src/TPCLoopers.cxx @@ -126,6 +126,40 @@ namespace o2 namespace eventgen { +namespace +{ +// Radial limits of the region from which a looper can still reach the TPC +// sensitive gas. The field cage positions are those used by the +// "ExcludeFCGap" selection in o2::tpc::Detector::ProcessHits() +// A looper can enter the sensitive gas from just outside it, so an additional margin is set +// with a factor two over the largest radial excursion which was observed in validation (~4.2 cm). +// +// No cut is applied on z because loopers spiral the field lines and a vertex as far as |z| = 283 cm +// feeds hits into the gas. A cut here would discard loopers that produce TPC signals. +constexpr double kFcLxIn = 82.428409; // cm, inner field cage strips +constexpr double kRodROut = 254.25 + 2.2; // cm, outer field cage rods plus their radial size +constexpr double kLooperRadialReach = 10.; // cm, margin for the helix sweep +constexpr double kTPCActiveRMin = kFcLxIn - kLooperRadialReach; +constexpr double kTPCActiveRMax = kRodROut + kLooperRadialReach; +} // namespace + +bool GenTPCLoopers::isInTPCActiveVolume(double vx, double vy) const +{ + const double vt = std::sqrt(vx * vx + vy * vy); + return (vt >= kTPCActiveRMin && vt <= kTPCActiveRMax); +} + +void GenTPCLoopers::setGeomProtection(bool protect) +{ + mGeomProtection = protect; + if (mGeomProtection) { + LOG(debug) << "TPC loopers geometrical protection: ON (accepting vertices with " + << kTPCActiveRMin << " <= Vt <= " << kTPCActiveRMax << " cm)"; + } else { + LOG(warning) << "TPC loopers geometrical protection: OFF - loopers will be generated outside the TPC active volume as well."; + } +} + GenTPCLoopers::GenTPCLoopers(std::string model_pairs, std::string model_compton, std::string poisson, std::string gauss, std::string scaler_pair, std::string scaler_compton) @@ -267,11 +301,20 @@ std::vector GenTPCLoopers::importParticles() std::vector particles; const double mass_e = TDatabasePDG::Instance()->GetParticle(11)->Mass(); const double mass_p = TDatabasePDG::Instance()->GetParticle(-11)->Mass(); + mNSkippedPairs = 0; + mNSkippedCompton = 0; // Get looper pairs from the event for (auto& pair : mGenPairs) { double px_e, py_e, pz_e, px_p, py_p, pz_p; double vx, vy, vz, time; double e_etot, p_etot; + // The generative model is not currently fully constrained to the TPC geometry, so it places + // significant fraction of the vertices outside the drift gas. + // These are now dropped before they reach the transport. + if (mGeomProtection && !isInTPCActiveVolume(pair[6], pair[7])) { + mNSkippedPairs++; + continue; + } px_e = pair[0]; py_e = pair[1]; pz_e = pair[2]; @@ -301,6 +344,10 @@ std::vector GenTPCLoopers::importParticles() double px, py, pz; double vx, vy, vz, time; double etot; + if (mGeomProtection && !isInTPCActiveVolume(compton[3], compton[4])) { + mNSkippedCompton++; + continue; + } px = compton[0]; py = compton[1]; pz = compton[2]; diff --git a/Steer/DigitizerWorkflow/src/IOTOFDigitizerSpec.cxx b/Steer/DigitizerWorkflow/src/IOTOFDigitizerSpec.cxx index 97f292332ecdb..008b2bff841c5 100644 --- a/Steer/DigitizerWorkflow/src/IOTOFDigitizerSpec.cxx +++ b/Steer/DigitizerWorkflow/src/IOTOFDigitizerSpec.cxx @@ -59,7 +59,6 @@ class IOTOFDPLDigitizerTask : o2::base::BaseDPLDigitizer geom->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); // make sure L2G matrices are loaded mDigitizer.setGeometry(geom); - mDigitizer.setChargeThreshold(-1000.f); mDigitizer.init(); } diff --git a/Steer/DigitizerWorkflow/src/SimpleDigitizerWorkflow.cxx b/Steer/DigitizerWorkflow/src/SimpleDigitizerWorkflow.cxx index bcc9a4ecddc37..7ba04725d928b 100644 --- a/Steer/DigitizerWorkflow/src/SimpleDigitizerWorkflow.cxx +++ b/Steer/DigitizerWorkflow/src/SimpleDigitizerWorkflow.cxx @@ -665,11 +665,18 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) if (isEnabled(o2::detectors::DetID::TRK)) { detList.emplace_back(o2::detectors::DetID::TRK); // connect the ALICE 3 TRK digitization - specs.emplace_back(o2::trk::getTRKDigitizerSpec(fanoutsize++, mctruth)); + specs.emplace_back(o2::trkft3::getTRKDigitizerSpec(fanoutsize++, mctruth)); // connect the ALICE 3 TRK digit writer specs.emplace_back(o2::trk::getTRKDigitWriterSpec(mctruth)); } + // the ALICE 3 FT3 part + if (isEnabled(o2::detectors::DetID::FT3)) { + detList.emplace_back(o2::detectors::DetID::FT3); + specs.emplace_back(o2::trkft3::getFT3DigitizerSpec(fanoutsize++, mctruth)); + specs.emplace_back(o2::trk::getFT3DigitWriterSpec(mctruth)); + } + // the ALICE 3 IOTOF part if (isEnabled(o2::detectors::DetID::TF3)) { detList.emplace_back(o2::detectors::DetID::TF3); diff --git a/Steer/DigitizerWorkflow/src/TRKDigitizerSpec.cxx b/Steer/DigitizerWorkflow/src/TRKDigitizerSpec.cxx index 06d922cc1a117..bf95164cecfd4 100644 --- a/Steer/DigitizerWorkflow/src/TRKDigitizerSpec.cxx +++ b/Steer/DigitizerWorkflow/src/TRKDigitizerSpec.cxx @@ -18,16 +18,18 @@ #include "Framework/Lifetime.h" #include "Framework/Task.h" #include "Steer/HitProcessingManager.h" -#include "DataFormatsITSMFT/Digit.h" +#include "DataFormatsTRKFT3/Digit.h" +#include "DataFormatsTRKFT3/Hit.h" #include "SimulationDataFormat/ConstMCTruthContainer.h" #include "DetectorsBase/BaseDPLDigitizer.h" #include "DetectorsRaw/HBFUtils.h" #include "DetectorsCommonDataFormats/DetID.h" #include "DetectorsCommonDataFormats/SimTraits.h" #include "DataFormatsParameters/GRPObject.h" -#include "DataFormatsITSMFT/ROFRecord.h" -#include "TRKSimulation/Digitizer.h" -#include "TRKSimulation/DPLDigitizerParam.h" +#include "DataFormatsTRKFT3/ROFRecord.h" +#include "TRKFT3Simulation/Digitizer.h" +#include "TRKFT3Simulation/DPLDigitizerParam.h" +#include "FT3Base/GeometryTGeo.h" #include "TRKBase/AlmiraParam.h" #include "TRKBase/GeometryTGeo.h" #include "TRKBase/Specs.h" @@ -45,14 +47,13 @@ using SubSpecificationType = o2::framework::DataAllocator::SubSpecificationType; namespace { -std::vector makeOutChannels(o2::header::DataOrigin detOrig, bool mctruth) +std::vector makeOutChannels(o2::header::DataOrigin detOrig, int nLayers, bool mctruth) { std::vector outputs; - for (uint32_t iLayer = 0; iLayer < o2::trk::AlmiraParam::getNLayers(); ++iLayer) { + for (uint32_t iLayer = 0; iLayer < static_cast(nLayers); ++iLayer) { outputs.emplace_back(detOrig, "DIGITS", iLayer, Lifetime::Timeframe); outputs.emplace_back(detOrig, "DIGITSROF", iLayer, Lifetime::Timeframe); if (mctruth) { - outputs.emplace_back(detOrig, "DIGITSMC2ROF", iLayer, Lifetime::Timeframe); outputs.emplace_back(detOrig, "DIGITSMCTR", iLayer, Lifetime::Timeframe); } } @@ -61,15 +62,30 @@ std::vector makeOutChannels(o2::header::DataOrigin detOrig, bool mct } } // namespace -namespace o2::trk +namespace o2::trkft3 { using namespace o2::base; -class TRKDPLDigitizerTask : BaseDPLDigitizer + +template +int getNLayers() +{ + if constexpr (N == o2::detectors::DetID::TRK) { + return o2::trk::AlmiraParam::getNLayers(); + } else { + return o2::trk::constants::MLOTDisks::nLayers; + } +} + +template +class TRKFT3DPLDigitizerTask : BaseDPLDigitizer { public: + static_assert(N == o2::detectors::DetID::TRK || N == o2::detectors::DetID::FT3, "only TRK and FT3 digitizers are supported"); + static constexpr o2::detectors::DetID ID{N == o2::detectors::DetID::TRK ? o2::detectors::DetID::TRK : o2::detectors::DetID::FT3}; + static constexpr o2::header::DataOrigin Origin{N == o2::detectors::DetID::TRK ? o2::header::gDataOriginTRK : o2::header::gDataOriginFT3}; using BaseDPLDigitizer::init; - TRKDPLDigitizerTask(bool mctruth = true) : BaseDPLDigitizer(InitServices::FIELD | InitServices::GEOM), mWithMCTruth(mctruth) {} + TRKFT3DPLDigitizerTask(bool mctruth = true) : BaseDPLDigitizer(InitServices::FIELD | InitServices::GEOM), mWithMCTruth(mctruth) {} void initDigitizerTask(framework::InitContext& ic) override { @@ -88,7 +104,7 @@ class TRKDPLDigitizerTask : BaseDPLDigitizer // read collision context from input auto context = pc.inputs().get("collisioncontext"); - context->initSimChains(mID, mSimChains); + context->initSimChains(ID, mSimChains); const bool withQED = context->isQEDProvided() && !mDisableQED; auto& timesview = context->getEventRecords(withQED); LOG(info) << "GOT " << timesview.size() << " COLLISION TIMES"; @@ -100,7 +116,7 @@ class TRKDPLDigitizerTask : BaseDPLDigitizer } TStopwatch timer; timer.Start(); - LOG(info) << " CALLING TRK DIGITIZATION "; + LOG(info) << " CALLING " << ID.getName() << " DIGITIZATION "; auto& eventParts = context->getEventParts(withQED); uint64_t nDigits{0}; @@ -111,7 +127,6 @@ class TRKDPLDigitizerTask : BaseDPLDigitizer if (mWithMCTruth) { mLabels[iLayer].clear(); mLabelsAccum[iLayer].clear(); - mMC2ROFRecordsAccum[iLayer].clear(); } mDigitizer.setDigits(&mDigits[iLayer]); @@ -120,7 +135,7 @@ class TRKDPLDigitizerTask : BaseDPLDigitizer mDigitizer.resetROFrameBounds(); // digits are directly put into DPL owned resource - auto& digitsAccum = pc.outputs().make>(Output{mOrigin, "DIGITS", iLayer}); + auto& digitsAccum = pc.outputs().make>(Output{Origin, "DIGITS", iLayer}); const int roFrameLengthInBC = mDigitizer.getParams().getROFrameLengthInBC(iLayer); const int nROFsPerOrbit = o2::constants::lhc::LHCMaxBunches / roFrameLengthInBC; @@ -162,7 +177,7 @@ class TRKDPLDigitizerTask : BaseDPLDigitizer mDigitizer.resetEventROFrames(); for (auto& part : eventParts[collID]) { mHits.clear(); - context->retrieveHits(mSimChains, o2::detectors::SimTraits::DETECTORBRANCHNAMES[mID][0].c_str(), part.sourceID, part.entryID, &mHits); + context->retrieveHits(mSimChains, o2::detectors::SimTraits::DETECTORBRANCHNAMES[ID][0].c_str(), part.sourceID, part.entryID, &mHits); if (!mHits.empty()) { LOG(debug) << "For collision " << collID << " eventID " << part.entryID @@ -170,16 +185,13 @@ class TRKDPLDigitizerTask : BaseDPLDigitizer mDigitizer.process(&mHits, part.entryID, part.sourceID, iLayer); } } - if (mWithMCTruth) { - mMC2ROFRecordsAccum[iLayer].emplace_back(collID, -1, mDigitizer.getEventROFrameMin(), mDigitizer.getEventROFrameMax()); - } accumulate(); } mDigitizer.fillOutputContainer(0xffffffff, iLayer); accumulate(); nDigits += digitsAccum.size(); - std::vector expDigitRofVec(nROFsTF); + std::vector expDigitRofVec(nROFsTF); for (int iROF = 0; iROF < nROFsTF; ++iROF) { auto& rof = expDigitRofVec[iROF]; const int orb = iROF * roFrameLengthInBC / o2::constants::lhc::LHCMaxBunches + mFirstOrbitTF; @@ -213,36 +225,16 @@ class TRKDPLDigitizerTask : BaseDPLDigitizer prevFirst = rof.getFirstEntry(); } - pc.outputs().snapshot(Output{mOrigin, "DIGITSROF", iLayer}, expDigitRofVec); + pc.outputs().snapshot(Output{Origin, "DIGITSROF", iLayer}, expDigitRofVec); if (mWithMCTruth) { - std::vector clippedMC2ROFRecords; - clippedMC2ROFRecords.reserve(mMC2ROFRecordsAccum[iLayer].size()); - for (auto mc2rof : mMC2ROFRecordsAccum[iLayer]) { - if (mc2rof.minROF >= static_cast(nROFsTF) || mc2rof.minROF > mc2rof.maxROF) { - mc2rof.rofRecordID = -1; - mc2rof.minROF = 0; - mc2rof.maxROF = 0; - } else { - mc2rof.maxROF = std::min(mc2rof.maxROF, nROFsTF - 1); - if (mc2rof.minROF > mc2rof.maxROF) { - mc2rof.rofRecordID = -1; - mc2rof.minROF = 0; - mc2rof.maxROF = 0; - } else { - mc2rof.rofRecordID = mc2rof.minROF; - } - } - clippedMC2ROFRecords.push_back(mc2rof); - } - pc.outputs().snapshot(Output{mOrigin, "DIGITSMC2ROF", iLayer}, clippedMC2ROFRecords); - auto& sharedlabels = pc.outputs().make>(Output{mOrigin, "DIGITSMCTR", iLayer}); + auto& sharedlabels = pc.outputs().make>(Output{Origin, "DIGITSMCTR", iLayer}); mLabelsAccum[iLayer].flatten_to(sharedlabels); mLabels[iLayer].clear_andfreememory(); mLabelsAccum[iLayer].clear_andfreememory(); } } - LOG(info) << mID.getName() << ": Sending ROMode= " << mROMode << " to GRPUpdater"; - pc.outputs().snapshot(Output{mOrigin, "ROMode", 0}, mROMode); + LOG(info) << ID.getName() << ": Sending ROMode= " << mROMode << " to GRPUpdater"; + pc.outputs().snapshot(Output{Origin, "ROMode", 0}, mROMode); timer.Stop(); LOG(info) << "Digitization took " << timer.CpuTime() << "s"; @@ -270,32 +262,40 @@ class TRKDPLDigitizerTask : BaseDPLDigitizer initOnce = true; auto& digipar = mDigitizer.getParams(); - // configure digitizer - o2::trk::GeometryTGeo* geom = o2::trk::GeometryTGeo::Instance(); - geom->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); // make sure L2G matrices are loaded - geom->Print(); - mDigitizer.setGeometry(geom); - - const auto& dopt = o2::trk::DPLDigitizerParam::Instance(); - // pc.inputs().get("TRK_almiraparam"); + const auto& dopt = o2::trkft3::DPLDigitizerParam::Instance(); const auto& aopt = o2::trk::AlmiraParam::Instance(); - mLayers = constants::VD::petal::nLayers + geom->getNumberOfLayersMLOT(); + if constexpr (N == o2::detectors::DetID::TRK) { + auto* geom = o2::trk::GeometryTGeo::Instance(); + geom->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); + geom->Print(); + mDigitizer.setGeometry(geom); + mLayers = o2::trk::AlmiraParam::getNLayers(); + } else { + auto* geom = o2::ft3::GeometryTGeo::Instance(); + geom->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); + geom->Print(); + mDigitizer.setGeometry(geom); + mLayers = getNLayers(); + } + if (mLayers > static_cast(o2::trkft3::DigiParams::getMaxLayers())) { + LOGP(fatal, "{} geometry has {} layers, but DigiParams supports at most {}", ID.getName(), mLayers, o2::trkft3::DigiParams::getMaxLayers()); + } mDigits.resize(mLayers); mROFRecords.resize(mLayers); mROFRecordsAccum.resize(mLayers); mLabels.resize(mLayers); mLabelsAccum.resize(mLayers); - mMC2ROFRecordsAccum.resize(mLayers); for (int iLayer = 0; iLayer < mLayers; ++iLayer) { - const auto roFrameLengthInBC = aopt.getROFLengthInBC(iLayer); + const int parLayer = std::min(iLayer, o2::trk::AlmiraParam::getNLayers() - 1); + const auto roFrameLengthInBC = aopt.getROFLengthInBC(parLayer); const auto frameNS = roFrameLengthInBC * o2::constants::lhc::LHCBunchSpacingNS; digipar.setROFrameLengthInBC(roFrameLengthInBC, iLayer); // ROF delay is treated as an additional bias from the digitizer point of view. - digipar.setROFrameBiasInBC(aopt.getROFBiasInBC(iLayer) + aopt.getROFDelayInBC(iLayer), iLayer); - digipar.setStrobeDelay(aopt.getStrobeDelay(iLayer), iLayer); - const auto strobeLengthCont = aopt.getStrobeLengthCont(iLayer); - digipar.setStrobeLength(strobeLengthCont > 0 ? strobeLengthCont : frameNS - aopt.getStrobeDelay(iLayer), iLayer); + digipar.setROFrameBiasInBC(aopt.getROFBiasInBC(parLayer) + aopt.getROFDelayInBC(parLayer), iLayer); + digipar.setStrobeDelay(aopt.getStrobeDelay(parLayer), iLayer); + const auto strobeLengthCont = aopt.getStrobeLengthCont(parLayer); + digipar.setStrobeLength(strobeLengthCont > 0 ? strobeLengthCont : frameNS - aopt.getStrobeDelay(parLayer), iLayer); digipar.setROFrameLength(frameNS, iLayer); } // parameters of signal time response: flat-top duration, max rise time and q @ which rise time is 0 @@ -306,12 +306,12 @@ class TRKDPLDigitizerTask : BaseDPLDigitizer digipar.setNSimSteps(dopt.nSimSteps); mROMode = o2::parameters::GRPObject::CONTINUOUS; - LOG(info) << mID.getName() << " simulated in CONTINUOUS RO mode"; + LOG(info) << ID.getName() << " simulated in CONTINUOUS RO mode"; // if (oTRKParams::Instance().useDeadChannelMap) { // pc.inputs().get("TRK_dead"); // trigger final ccdb update // } - pc.inputs().get("TRK_aptsresp"); + pc.inputs().get((std::string(ID.getName()) + "_aptsresp").c_str()); // init digitizer mDigitizer.init(); @@ -321,8 +321,8 @@ class TRKDPLDigitizerTask : BaseDPLDigitizer void finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) { - if (matcher == ConcreteDataMatcher(mOrigin, "ALMIRAPARAM", 0)) { - LOG(info) << mID.getName() << " Almira param updated"; + if (matcher == ConcreteDataMatcher(Origin, "ALMIRAPARAM", 0)) { + LOG(info) << ID.getName() << " Almira param updated"; const auto& par = o2::trk::AlmiraParam::Instance(); par.printKeyValues(); return; @@ -332,8 +332,8 @@ class TRKDPLDigitizerTask : BaseDPLDigitizer // mDigitizer.setDeadChannelsMap((o2::itsmft::NoiseMap*)obj); // return; // } - if (matcher == ConcreteDataMatcher(mOrigin, "APTSRESP", 0)) { - LOG(info) << mID.getName() << " loaded APTSResponseData"; + if (matcher == ConcreteDataMatcher(Origin, "APTSRESP", 0)) { + LOG(info) << ID.getName() << " loaded APTSResponseData"; if (mLocalRespFile.empty()) { LOG(info) << "Using CCDB/APTS response file"; mDigitizer.getParams().setResponse((const o2::itsmft::AlpideSimResponse*)obj); @@ -352,18 +352,15 @@ class TRKDPLDigitizerTask : BaseDPLDigitizer bool mDisableQED{false}; unsigned long mFirstOrbitTF = 0x0; std::string mLocalRespFile{""}; - const o2::detectors::DetID mID{o2::detectors::DetID::TRK}; - const o2::header::DataOrigin mOrigin{o2::header::gDataOriginTRK}; - o2::trk::Digitizer mDigitizer{}; + o2::trkft3::Digitizer mDigitizer{}; int mLayers{0}; - std::vector> mDigits{}; - std::vector> mROFRecords{}; - std::vector> mROFRecordsAccum{}; - std::vector mHits{}; - std::vector* mHitsP{&mHits}; + std::vector> mDigits{}; + std::vector> mROFRecords{}; + std::vector> mROFRecordsAccum{}; + std::vector mHits{}; + std::vector* mHitsP{&mHits}; std::vector> mLabels{}; std::vector> mLabelsAccum{}; - std::vector> mMC2ROFRecordsAccum{}; std::vector mSimChains{}; o2::parameters::GRPObject::ROMode mROMode = o2::parameters::GRPObject::PRESENT; // readout mode }; @@ -381,11 +378,27 @@ DataProcessorSpec getTRKDigitizerSpec(int channel, bool mctruth) inputs.emplace_back("TRK_aptsresp", "TRK", "APTSRESP", 0, Lifetime::Condition, ccdbParamSpec("IT3/Calib/APTSResponse")); return DataProcessorSpec{detStr + "Digitizer", - inputs, makeOutChannels(detOrig, mctruth), - AlgorithmSpec{adaptFromTask(mctruth)}, + inputs, makeOutChannels(detOrig, getNLayers(), mctruth), + AlgorithmSpec{adaptFromTask>(mctruth)}, + Options{ + {"disable-qed", o2::framework::VariantType::Bool, false, {"disable QED handling"}}, + {"local-response-file", o2::framework::VariantType::String, "", {"use response file saved locally at this path/filename"}}}}; +} + +DataProcessorSpec getFT3DigitizerSpec(int channel, bool mctruth) +{ + std::string detStr = o2::detectors::DetID::getName(o2::detectors::DetID::FT3); + auto detOrig = o2::header::gDataOriginFT3; + std::vector inputs; + inputs.emplace_back("collisioncontext", "SIM", "COLLISIONCONTEXT", static_cast(channel), Lifetime::Timeframe); + inputs.emplace_back("FT3_aptsresp", "FT3", "APTSRESP", 0, Lifetime::Condition, ccdbParamSpec("IT3/Calib/APTSResponse")); + + return DataProcessorSpec{detStr + "Digitizer", + inputs, makeOutChannels(detOrig, getNLayers(), mctruth), + AlgorithmSpec{adaptFromTask>(mctruth)}, Options{ {"disable-qed", o2::framework::VariantType::Bool, false, {"disable QED handling"}}, {"local-response-file", o2::framework::VariantType::String, "", {"use response file saved locally at this path/filename"}}}}; } -} // namespace o2::trk +} // namespace o2::trkft3 diff --git a/Steer/DigitizerWorkflow/src/TRKDigitizerSpec.h b/Steer/DigitizerWorkflow/src/TRKDigitizerSpec.h index 5a1a59c3b9f5e..e28401fd14389 100644 --- a/Steer/DigitizerWorkflow/src/TRKDigitizerSpec.h +++ b/Steer/DigitizerWorkflow/src/TRKDigitizerSpec.h @@ -14,11 +14,12 @@ #include "Framework/DataProcessorSpec.h" -namespace o2::trk +namespace o2::trkft3 { o2::framework::DataProcessorSpec getTRKDigitizerSpec(int channel, bool mctruth = true); +o2::framework::DataProcessorSpec getFT3DigitizerSpec(int channel, bool mctruth = true); } -// namespace o2::trk +// namespace o2::trkft3 // end namespace o2 #endif diff --git a/Steer/include/Steer/O2MCApplicationBase.h b/Steer/include/Steer/O2MCApplicationBase.h index d61199baba0ae..bd730c0f2fcb2 100644 --- a/Steer/include/Steer/O2MCApplicationBase.h +++ b/Steer/include/Steer/O2MCApplicationBase.h @@ -68,6 +68,11 @@ class O2MCApplicationBase : public FairMCApplication // keeping track of volumeIds and volume names double mLongestTrackTime = 0; + bool mTrackSeedWarned{false}; // whether we already complained that seeding never fired + + /// whether this engine needs per-track seeding in PreTrack (Geant3 seeds at + /// stack-pop time instead, see O2MCApplicationBase::seedsInPreTrack) + bool seedsInPreTrack() const; /// some common parts of finishEvent void finishEventCommon(); TrackRefFcn mTrackRefFcn; // a function hook that gets (optionally) called during Stepping diff --git a/Steer/src/CollisionContextTool.cxx b/Steer/src/CollisionContextTool.cxx index e97eeada3fd0c..f0abe6b7c5d03 100644 --- a/Steer/src/CollisionContextTool.cxx +++ b/Steer/src/CollisionContextTool.cxx @@ -440,6 +440,10 @@ int main(int argc, char* argv[]) // for now construct a specific CCDBManager for this query o2::ccdb::CCDBManagerInstance ccdb_inst(ccdb_info.server + std::string(":") + ccdb_info.port); ccdb_inst.setFatalWhenNull(false); + // this is a private instance, so it does not inherit the time-machine + // constraint that BasicCCDBManager picks up from the environment; + // carry it over explicitly (a 0 here means "unconstrained" anyway) + ccdb_inst.setCreatedNotAfter(o2::ccdb::BasicCCDBManager::instance().getCreatedNotAfter()); auto local_hist = ccdb_inst.getForTimeStamp(ccdb_info.fullPath, options.timestamp); if (local_hist) { // case in which CCDB object contains directly a ROOT histogram diff --git a/Steer/src/O2MCApplication.cxx b/Steer/src/O2MCApplication.cxx index 1e3f925042d01..7f37fd4bccf03 100644 --- a/Steer/src/O2MCApplication.cxx +++ b/Steer/src/O2MCApplication.cxx @@ -35,6 +35,9 @@ #include #include #include "SimConfig/GlobalProcessCutSimParam.h" +#include +#include // full type: FairField derives from TVirtualMagField +#include #include "DetectorsBase/GeometryManagerParam.h" #include #include @@ -43,6 +46,10 @@ #include #include #include "SimConfig/G4Params.h" +#include "DetectorsBase/VMCSeederService.h" // per-track seeding of the engine +#include +#include +#include namespace o2 { @@ -116,14 +123,96 @@ void O2MCApplicationBase::Stepping() FairMCApplication::Stepping(); } +namespace +{ +// Hash of a track's initial state (vertex, global time, momentum, PDG). Used as +// the random seed for that track, so that a track's random stream depends only +// on the track itself and not on how many randoms earlier tracks happened to +// consume. +// +// The values are read from the transport engine, not from +// o2::data::Stack::GetCurrentTrack(): under Geant4 the stack's "current track" +// is only meaningful for primaries -- Stack::SetCurrentTrack() falls back to +// mCurrentParticle0 (the last particle *pushed*) for anything beyond the +// primary array, so every secondary would hash the wrong particle. Both engines +// have the track's initial state loaded by the time PreTrack is called (Geant4 +// sets the step to kVertex first; Geant3 calls GLTRAC before GUTRAK). +ULong_t hashCurrentTrack(TVirtualMC* vmc) +{ + auto asLong = [](double x) { + ULong_t l; + std::memcpy(&l, &x, sizeof(l)); + return l; + }; + + TLorentzVector pos, mom; + vmc->TrackPosition(pos); + vmc->TrackMomentum(mom); + + ULong_t hash = asLong(pos.X()); + hash ^= asLong(pos.Y()); + hash ^= asLong(pos.Z()); + hash ^= asLong(pos.T()); + hash ^= asLong(mom.Px()); + hash ^= asLong(mom.Py()); + hash ^= asLong(mom.Pz()); + hash += (ULong_t)vmc->TrackPid(); + return hash; +} +} // namespace + +bool O2MCApplicationBase::seedsInPreTrack() const +{ + // Geant3 seeds at stack-pop time, in o2::data::Stack::PopNextTrack(). That is + // strictly earlier than its PreTrack hook (gutrak). + // Do not seed Geant3 here as well -- it is already covered, and reseeding a + // second time mid-track would undo the first. + static const bool inPreTrack = [this]() { + const char* name = (fMC != nullptr) ? fMC->GetName() : ""; + return strncmp(name, "TGeant3", 7) != 0; + }(); + return inPreTrack; +} + void O2MCApplicationBase::PreTrack() { - // dispatch first to function in FairRoot + if (mCutParams.trackSeed && seedsInPreTrack()) { + // Per-track seeding for engines that do not go through + // o2::data::Stack::PopNextTrack(). Geant4 is one: it takes primaries via + // PopPrimaryForTracking and keeps secondaries internally, so the stack hook + // never fires and this is the only per-track hook available. It is called + // for primaries and secondaries alike + // (TG4TrackingAction::PreUserTrackingAction), and only on a track's first + // step, so a suspended track is not reseeded mid-flight. + auto hash = hashCurrentTrack(fMC); + // TRandom::SetSeed(0) means "seed from the clock" -- never let that happen. + gRandom->SetSeed(hash == 0 ? 1 : hash); + o2::base::VMCSeederService::instance().setSeed(); + } + + // dispatch now to function in FairRoot FairMCApplication::PreTrack(); } void O2MCApplicationBase::ConstructGeometry() { + // The transport engine constructs the geometry from inside its own + // constructor, long before FairMCApplication::InitMC() attaches the magnetic + // field to it. The media built below read the field through + // Detector::initFieldTrackingParams(), so without this they all silently fall + // back to hardcoded defaults. The run has known the field since + // build_geometry.C, which runs before Init() -- hand it over now. + if (auto* vmc = TVirtualMC::GetMC(); vmc != nullptr && vmc->GetMagField() == nullptr) { + auto* run = FairRunSim::Instance(); + if (run != nullptr && run->GetField() != nullptr) { + vmc->SetMagField(run->GetField()); + LOG(info) << "Magnetic field attached to the engine before media creation"; + } else { + LOG(warn) << "No magnetic field available at geometry construction; media " + "will be initialised with default tracking parameters"; + } + } + // fill the mapping mModIdToName.clear(); o2::detectors::DetID::mask_t dmask{}; @@ -289,6 +378,17 @@ void O2MCApplicationBase::finishEventCommon() header->setDetId2HitBitLUT(o2::base::Detector::getDetId2HitBitIndex()); static_cast(GetStack())->updateEventStats(); + + // Per-track seeding used to be wired to a stack callback that one of the two + // engines never invoked, and it failed silently. Never again: if it was asked + // for and nothing was seeded, say so. + if (mCutParams.trackSeed && o2::base::VMCSeederService::instance().getSeedCount() == 0 && + !mTrackSeedWarned) { + mTrackSeedWarned = true; + LOG(warn) << "Per-track seeding (SimCutParams.trackSeed) was requested but not a single track " + "was seeded -- neither the stack nor the PreTrack hook fired for this engine. " + "Seeding is NOT active."; + } } void O2MCApplicationBase::FinishEvent() @@ -410,10 +510,10 @@ void addSpecialParticles() TVirtualMC::GetMC()->DefineParticle(-1030010020, "AntiOmegaNeutron", kPTHadron, 2.472, 1.0, 2.190e-22, "Hadron", 0.0, 2, 1, 0, 0, 0, 0, 0, 2, kFALSE); //Omega-Omega - TVirtualMC::GetMC()->DefineParticle(1060020020, "OmegaOmega", kPTHadron, 3.229, 2.0, 2.632e-10, "Hadron", 0.0, 0, 1, 0, 0, 0, 0, 0, 2, kFALSE); + TVirtualMC::GetMC()->DefineParticle(1060020020, "OmegaOmega", kPTHadron, 3.343, -2.0, 8.21e-11, "Hadron", 0.0, 0, 1, 0, 0, 0, 0, 0, 2, kFALSE); //Anti-Omega-Omega - TVirtualMC::GetMC()->DefineParticle(-1060020020, "AntiOmegaOmega", kPTHadron, 3.229, 2.0, 2.632e-10, "Hadron", 0.0, 0, 1, 0, 0, 0, 0, 0, 2, kFALSE); + TVirtualMC::GetMC()->DefineParticle(-1060020020, "AntiOmegaOmega", kPTHadron, 3.343, 2.0, 8.21e-11, "Hadron", 0.0, 0, 1, 0, 0, 0, 0, 0, 2, kFALSE); //Lambda(1405)-Proton TVirtualMC::GetMC()->DefineParticle(1010010021, "Lambda1405Proton", kPTHadron, 2.295, 1.0, 1.316e-23, "Hadron", 0.0, 0, 1, 0, 0, 0, 0, 0, 2, kFALSE); @@ -1183,6 +1283,7 @@ void addSpecialParticles() TVirtualMC::GetMC()->SetDecayMode(-1030010020, abratio8, amode8); // Define the 3-body phase space decay for the Omega-Omega + // Assuming that one of the Omegas decays freely inside the nucleus Int_t mode9[6][3]; Float_t bratio9[6]; @@ -1192,9 +1293,18 @@ void addSpecialParticles() mode9[kz][1] = 0; mode9[kz][2] = 0; } - bratio9[0] = 100.; + bratio9[0] = 68.; mode9[0][0] = 3334; // negative Omega - mode9[0][1] = 3312; // negative Xi + mode9[0][1] = 3122; // Lambda + mode9[0][2] = -321; // negative Kaon + bratio9[1] = 24; + mode9[1][0] = 3334; // negative Omega + mode9[1][1] = 3322; // neutral Xi + mode9[1][2] = -211; // negative pion + bratio9[2] = 8.; + mode9[2][0] = 3334; // negative Omega + mode9[2][1] = 3312; // negative Xi + mode9[2][2] = 111; // neutral pion TVirtualMC::GetMC()->SetDecayMode(1060020020, bratio9, mode9); @@ -1208,9 +1318,18 @@ void addSpecialParticles() amode9[kz][1] = 0; amode9[kz][2] = 0; } - abratio9[0] = 100.; + abratio9[0] = 68.; amode9[0][0] = -3334; // positive Omega - amode9[0][1] = -3312; // positive Xi + amode9[0][1] = -3122; // anti-Lambda + amode9[0][2] = 321; // positive Kaon + abratio9[1] = 24.; + amode9[1][0] = -3334; // positive Omega + amode9[1][1] = -3322; // anti-neutral Xi + amode9[1][2] = 211; // positive pion + abratio9[2] = 8.; + amode9[2][0] = -3334; // positive Omega + amode9[2][1] = -3312; // positive Xi + amode9[2][2] = 111; // neutral pion TVirtualMC::GetMC()->SetDecayMode(-1060020020, abratio9, amode9); diff --git a/Utilities/rANS/include/rANS/internal/common/defines.h b/Utilities/rANS/include/rANS/internal/common/defines.h index 21afb4ff01750..d053a2e67dab3 100644 --- a/Utilities/rANS/include/rANS/internal/common/defines.h +++ b/Utilities/rANS/include/rANS/internal/common/defines.h @@ -40,7 +40,7 @@ #error RANS_FMA cannot be directly set #endif -#if (defined(__x86_64__) || defined(__aarch64__)) +#if (defined(__x86_64__) || defined(__aarch64__) || (defined(__riscv) && __riscv_xlen == 64)) #define RANS_COMPAT #if defined(__SIZEOF_INT128__) #define RANS_SINGLE_STREAM diff --git a/dependencies/FindO2GPU.cmake b/dependencies/FindO2GPU.cmake index b229f46422eb8..bd495138ab3a6 100644 --- a/dependencies/FindO2GPU.cmake +++ b/dependencies/FindO2GPU.cmake @@ -10,7 +10,7 @@ # or submit itself to any jurisdiction. # NOTE!!!! - Whenever this file is changed, move it over to alidist/resources -# FindO2GPU.cmake Version 16 +# FindO2GPU.cmake Version 18 set(CUDA_COMPUTETARGET_DEFAULT_FULL 80-real 86-real 89-real 120-real 75-virtual) set(HIP_AMDGPUTARGET_DEFAULT_FULL gfx906;gfx908) @@ -54,9 +54,11 @@ function(detect_gpu_arch backend) # Detect GPU architecture, optionally filterri endif() if(CUDA_FIRST_TARGET GREATER_EQUAL 120) set(CUDA_TARGET BLACKWELL) + elseif(CUDA_FIRST_TARGET GREATER_EQUAL 90) + set(CUDA_TARGET HOPPER) elseif(CUDA_FIRST_TARGET GREATER_EQUAL 89) set(CUDA_TARGET ADA) - elseif(CUDA_FIRST_TARGET GREATER_EQUAL 86) + elseif(CUDA_FIRST_TARGET GREATER_EQUAL 80) set(CUDA_TARGET AMPERE) elseif(CUDA_FIRST_TARGET GREATER_EQUAL 75) set(CUDA_TARGET TURING) @@ -81,6 +83,8 @@ function(detect_gpu_arch backend) # Detect GPU architecture, optionally filterri string(REGEX MATCH "....$" HIP_FIRST_TARGET_PADDED "0000${HIP_FIRST_TARGET}") if(HIP_FIRST_TARGET_PADDED STRGREATER_EQUAL "1000") set(HIP_TARGET RDNA) + elseif(HIP_FIRST_TARGET_PADDED STRGREATER_EQUAL "0940") + set(HIP_TARGET MI300) elseif(HIP_FIRST_TARGET_PADDED STRGREATER_EQUAL "090a") set(HIP_TARGET MI210) elseif(HIP_FIRST_TARGET_PADDED STRGREATER_EQUAL "0908") @@ -314,7 +318,7 @@ if(ENABLE_HIP) set(CMAKE_HIP_STANDARD_REQUIRED TRUE) set(TMP_ROCM_DIR_LIST "${CMAKE_PREFIX_PATH}:$ENV{CMAKE_PREFIX_PATH}") string(REPLACE ":" ";" TMP_ROCM_DIR_LIST "${TMP_ROCM_DIR_LIST}") - list(FILTER TMP_ROCM_DIR_LIST INCLUDE REGEX rocm) + list(FILTER TMP_ROCM_DIR_LIST INCLUDE REGEX /rocm/lib/cmake) list(POP_FRONT TMP_ROCM_DIR_LIST TMP_ROCM_DIR) get_filename_component(TMP_ROCM_DIR ${TMP_ROCM_DIR}/../../ ABSOLUTE) if (NOT DEFINED CMAKE_HIP_COMPILER) diff --git a/macro/CMakeLists.txt b/macro/CMakeLists.txt index 0bb5650364b06..91a15af31c3b0 100644 --- a/macro/CMakeLists.txt +++ b/macro/CMakeLists.txt @@ -125,6 +125,7 @@ endif() o2_add_test_root_macro(build_geometry.C PUBLIC_LINK_LIBRARIES O2::SimConfig O2::DetectorsPassive + O2::ExternalDetectors O2::Field O2::MFTSimulation O2::MCHSimulation @@ -184,6 +185,7 @@ if(Geant4_FOUND AND BUILD_SIMULATION) o2_add_test_root_macro(o2sim.C PUBLIC_LINK_LIBRARIES O2::Generators O2::DetectorsPassive + O2::ExternalDetectors O2::Field O2::MFTSimulation O2::MCHSimulation diff --git a/macro/build_geometry.C b/macro/build_geometry.C index e46b45254d308..25349e5195727 100644 --- a/macro/build_geometry.C +++ b/macro/build_geometry.C @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -51,11 +52,11 @@ #endif #ifdef ENABLE_UPGRADES -#include #include #include #include #include +#include #include #include #include @@ -184,16 +185,18 @@ void build_geometry(FairRunSim* run = nullptr) } #endif - if (isActivated("EXT")) { - // EXAMPLE!! how to pick geometry generated from external (CAD) module via `O2_CADtoTGeo.py` - o2::passive::ExternalModuleOptions options; - options.root_macro_file = "PATH_TO_EXTERNAL_GEOM_MODULE/geom.C"; - options.anchor_volume = "barrel"; // hook this into barrel - auto rot = new TGeoCombiTrans(); - rot->RotateX(90); - rot->SetDy(30); // we need to compensate for a shift of barrel with respect to zero - options.placement = rot; - run->AddModule(new o2::passive::ExternalModule("FOO", "BAR", options)); + // external (e.g. CAD-derived) geometry modules are injected from the outside via a JSON + // description file given with `--extGeomFile` (geometry generated via `O2_CADtoTGeo.py`). + // Each module is added when its 'name' is part of the active module list (so it can be + // switched on/off via the detector list, like any other module). + if (auto extGeomFile = confref.getExtGeomFilename(); !extGeomFile.empty()) { + for (auto* extmod : o2::passive::ExternalModule::createFromJSON(extGeomFile)) { + if (isActivated(extmod->GetName())) { + run->AddModule(extmod); + } else { + delete extmod; // not requested in the active module list + } + } } // the absorber @@ -226,6 +229,19 @@ void build_geometry(FairRunSim* run = nullptr) } }; + // sensitive external (CAD-derived) detectors, injected from the same JSON used for passive + // external modules (entries under "externalDetectors"). These derive from o2::base::Detector, + // so they produce hits and participate in the regular hit forwarding/merging machinery. + if (auto extGeomFile = confref.getExtGeomFilename(); !extGeomFile.empty()) { + for (auto* extdet : o2::ext::ExternalDetector::createFromJSON(extGeomFile)) { + if (isActivated(extdet->GetName())) { + addReadoutDetector(extdet); + } else { + delete extdet; // not requested in the active module list + } + } + } + if (isActivated("TOF")) { // TOF addReadoutDetector(new o2::tof::Detector(isReadout("TOF"))); diff --git a/prodtests/full-system-test/calib-workflow.sh b/prodtests/full-system-test/calib-workflow.sh old mode 100755 new mode 100644 index 3c05ca6cda303..a14ff3b620d45 --- a/prodtests/full-system-test/calib-workflow.sh +++ b/prodtests/full-system-test/calib-workflow.sh @@ -23,7 +23,7 @@ fi if [[ "${CALIB_TPC_SCDCALIB_SENDTRKDATA:-}" == "1" ]]; then ENABLE_TRKDATA_OUTPUT="--send-track-data"; else ENABLE_TRKDATA_OUTPUT=""; fi # specific calibration workflows -if [[ $CALIB_TPC_SCDCALIB == 1 ]]; then add_W o2-tpc-scdcalib-interpolation-workflow "--vtx-sources $VERTEX_TRACK_MATCHING_SOURCES --tracking-sources $TRACK_SOURCES ${CALIB_TPC_SCDCALIB_SLOTLENGTH:+"--sec-per-slot $CALIB_TPC_SCDCALIB_SLOTLENGTH"} $ENABLE_TRKDATA_OUTPUT $DISABLE_ROOT_OUTPUT --disable-root-input --pipeline $(get_N tpc-track-interpolation TPC REST)"; fi +if [[ $CALIB_TPC_SCDCALIB == 1 ]]; then add_W o2-tpc-scdcalib-interpolation-workflow "--vtx-sources $VERTEX_TRACK_MATCHING_SOURCES --tracking-sources $TRACK_SOURCES ${CALIB_TPC_SCDCALIB_SLOTLENGTH:+"--sec-per-slot $CALIB_TPC_SCDCALIB_SLOTLENGTH"} $ENABLE_TRKDATA_OUTPUT $DISABLE_ROOT_OUTPUT $DISABLE_ROOT_INPUT --pipeline $(get_N tpc-track-interpolation TPC REST)"; fi if [[ $CALIB_TPC_TIMEGAIN == 1 ]]; then : ${SCALEEVENTS_TPC_TIMEGAIN:=40} : ${SCALETRACKS_TPC_TIMEGAIN:=1000} @@ -55,6 +55,10 @@ if [[ $CALIB_ASYNC_EXTRACTTPCCURRENTS == 1 ]]; then fi if [[ $CALIB_ASYNC_EXTRACTTIMESERIES == 1 ]] ; then : ${CALIB_ASYNC_SAMPLINGFACTORTIMESERIES:=0.001} + : ${TPCTIMESERIES_MIN_MOMENTUM:=0.2} + : ${TPCTIMESERIES_MIN_CLUSTER:=80} + : ${TPCTIMESERIES_MAX_TGL:=1.4} + : ${TPCTIMESERIES_MULT_MAX:=50000} if [[ -n ${CALIB_ASYNC_ENABLEUNBINNEDTIMESERIES:-} ]]; then CONFIG_TPCTIMESERIES+=" --enable-unbinned-root-output --sample-unbinned-tsallis --threads ${TPCTIMESERIES_THREADS:-1}" fi @@ -69,7 +73,11 @@ if [[ $CALIB_ASYNC_EXTRACTTIMESERIES == 1 ]] ; then fi : ${TPCTIMESERIES_SOURCES:=$TRACK_SOURCES} CONFIG_TPCTIMESERIES+=" --track-sources $TPCTIMESERIES_SOURCES" - add_W o2-tpc-time-series-workflow "${CONFIG_TPCTIMESERIES}" + CONFIG_TPCTIMESERIES+=" --min-momentum ${TPCTIMESERIES_MIN_MOMENTUM}" + CONFIG_TPCTIMESERIES+=" --min-cluster ${TPCTIMESERIES_MIN_CLUSTER}" + CONFIG_TPCTIMESERIES+=" --max-tgl ${TPCTIMESERIES_MAX_TGL}" + CONFIG_TPCTIMESERIES+=" --mult-max ${TPCTIMESERIES_MULT_MAX}" + add_W o2-tpc-time-series-workflow "$DISABLE_ROOT_INPUT ${CONFIG_TPCTIMESERIES}" fi # output-proxy for aggregator diff --git a/prodtests/full-system-test/dpl-workflow.sh b/prodtests/full-system-test/dpl-workflow.sh index 69ffcaf9ff378..439000af842d0 100755 --- a/prodtests/full-system-test/dpl-workflow.sh +++ b/prodtests/full-system-test/dpl-workflow.sh @@ -813,6 +813,7 @@ else echo "#export VERTEX_TRACK_MATCHING_SOURCES=$VERTEX_TRACK_MATCHING_SOURCES" echo "#export SVERTEXING_SOURCES=$SVERTEXING_SOURCES" echo "#export AOD_SOURCES=$AOD_SOURCES" + [[ -n ${O2_DPL_MVBIAS:-} ]] && echo "#export O2_DPL_MVBIAS=\"${O2_DPL_MVBIAS}\"" echo "\n\n#Workflow command:\n\n${WORKFLOW}\n" | sed -e "s/\\\\n/\n/g" -e"s/| */| \\\\\n/g" | eval cat $( [[ $WORKFLOWMODE == "dds" ]] && echo '1>&2') fi if [[ $WORKFLOWMODE != "print" ]]; then eval $WORKFLOW; else true; fi diff --git a/prodtests/sim_challenge.sh b/prodtests/sim_challenge.sh index a7c7e7f7993d7..a2cf44dc3a66d 100755 --- a/prodtests/sim_challenge.sh +++ b/prodtests/sim_challenge.sh @@ -276,7 +276,7 @@ if [ "$doreco" == "1" ]; then # echo "Return status of strangeness tracking: $?" echo "Producing AOD" - taskwrapper aod.log o2-aod-producer-workflow $gloOpt --aod-writer-keep dangling --aod-writer-resfile "AO2D" --aod-writer-resmode UPDATE --aod-timeframe-id 1 --run-number 300000 + taskwrapper aod.log o2-aod-producer-workflow $gloOpt --aod-writer-keep dangling --aod-writer-resfile "AO2D" --aod-writer-resmode UPDATE --aod-timeframe-id 1 --run-number 300000 --collect-config-files echo "Return status of AOD production: $?" # let's do some very basic analysis tests (mainly to enlarge coverage in full CI) and enabled when SIM_CHALLENGE_ANATESTING=ON diff --git a/run/CMakeLists.txt b/run/CMakeLists.txt index 474aa7e41eb7c..063745e757816 100644 --- a/run/CMakeLists.txt +++ b/run/CMakeLists.txt @@ -22,6 +22,7 @@ target_link_libraries(allsim FairMQ::FairMQ O2::CPVSimulation O2::DetectorsPassive + O2::ExternalDetectors O2::EMCALSimulation O2::FDDSimulation O2::Field @@ -46,7 +47,7 @@ target_link_libraries(allsim $<$:O2::IOTOFSimulation> $<$:O2::RICHSimulation> $<$:O2::ECalSimulation> - $<$:O2::FD3Simulation> + $<$:O2::FD3Simulation> $<$:O2::MI3Simulation> O2::Generators) diff --git a/run/O2HitMerger.h b/run/O2HitMerger.h index 2f54a8b5d202a..a0a79ac4fef96 100644 --- a/run/O2HitMerger.h +++ b/run/O2HitMerger.h @@ -59,6 +59,7 @@ #include #include #include +#include #include "CommonUtils/ShmManager.h" #include @@ -890,6 +891,7 @@ class O2HitMerger : public fair::mq::Device int mPipeToDriver = -1; std::vector> mDetectorInstances; //! + std::vector mExternalDetIDs; //! DetID slots occupied by external (CAD) detectors // output folder configuration std::string mInitialOutputDir; // initial output folder of the process (initialized during construction) @@ -900,6 +902,7 @@ class O2HitMerger : public fair::mq::Device // init detector instances void initDetInstances(); + void initExternalDetInstances(); void initHitFiles(std::string prefix); }; @@ -921,6 +924,12 @@ void O2HitMerger::initHitFiles(std::string prefix) // init the detector specific output files initHitTreeAndOutFile(prefix, i); } + + // external (CAD) detectors are not part of the readout-detector list (their module names + // are not DetID names); their slots were determined in initDetInstances() + for (auto detID : mExternalDetIDs) { + initHitTreeAndOutFile(prefix, detID); + } } // init detector instances used to write hit data to a TTree @@ -1052,6 +1061,57 @@ void O2HitMerger::initDetInstances() if (counter != DetID::nDetectors) { LOG(warning) << " O2HitMerger: Some Detectors are potentially missing in this initialization "; } + + // also register external (CAD-derived) sensitive detectors so their hits are persisted + // in parallel (multi-worker) mode + initExternalDetInstances(); +} + +// init detector instances for external (CAD-derived) sensitive detectors. +// These are not part of the hard-coded DetID switch above: they are described in the +// external geometry JSON (the same file used by build_geometry.C on the worker side) and +// tied to an existing (free) DetID. The merger only needs an instance able to interpret the +// generic o2::ext::Hit wire format and write the "Hit" branch; no geometry is built here. +void O2HitMerger::initExternalDetInstances() +{ + using o2::detectors::DetID; + + auto& simConfig = o2::conf::SimConfig::Instance(); + const auto extGeomFile = simConfig.getExtGeomFilename(); + if (extGeomFile.empty()) { + return; + } + + // mirror the worker-side activation: an external detector participates when its module + // name is part of the active module list + auto const& activeModules = simConfig.getActiveModules(); + auto isActivated = [&activeModules](std::string const& s) -> bool { + return std::find(activeModules.begin(), activeModules.end(), s) != activeModules.end(); + }; + + for (auto* extdet : o2::ext::ExternalDetector::createFromJSON(extGeomFile)) { + const std::string name = extdet->GetName(); + if (!isActivated(name)) { + delete extdet; // not requested in the active module list + continue; + } + const int detID = extdet->GetDetId(); + if (detID < DetID::First || detID > DetID::Last) { + LOG(error) << "O2HitMerger: external detector " << name << " has invalid DetID " << detID << "; skipping"; + delete extdet; + continue; + } + if (mDetectorInstances[detID]) { + LOG(error) << "O2HitMerger: DetID " << DetID::getName(detID) << " requested by external detector " << name + << " is already occupied; its hits will not be persisted. Assign a free DetID."; + delete extdet; + continue; + } + mDetectorInstances[detID].reset(extdet); + mExternalDetIDs.emplace_back(detID); + LOG(info) << "O2HitMerger: registered external detector " << name << " on DetID " << DetID::getName(detID) + << " (branch " << name << "Hit)"; + } } } // namespace devices diff --git a/run/SimExamples/External_Sensitive_Detectors/README.md b/run/SimExamples/External_Sensitive_Detectors/README.md new file mode 100644 index 0000000000000..d36a6efa5e297 --- /dev/null +++ b/run/SimExamples/External_Sensitive_Detectors/README.md @@ -0,0 +1,54 @@ +# External sensitive detectors + +A minimal example showing how to add **sensitive** detectors to `o2-sim` without compiling +anything into O2. Two artificial detectors are injected purely from data: + +| name | geometry (runtime macro) | DetID slot | sensitive action | +|---------|-------------------------------|------------|---------------------------------| +| `ACYL` | `geometry_innerCylinder.macro`| `ITS` | built-in entrance/exit action | +| `BDISK` | `geometry_outerDisk.macro` | `TST` | custom action `sensitive_action.macro` | + +Both are instances of the single compiled `o2::ext::ExternalDetector` class and produce the +single generic hit type `o2::ext::Hit`. They differ only in their data: geometry, the DetID +slot they occupy, and (optionally) a sensitive-action macro. + +## Run + +```bash +./run.sh +``` + +This transports a few `boxgen` events and prints the per-detector hit counts, e.g. + +``` +External sensitive detector hits: + ACYLHit : ~430 hits over 5 events, mean radius 20.0 cm [o2sim_HitsITS.root] + BDISKHit : ~120 hits over 5 events, mean radius ... cm [o2sim_HitsTST.root] +``` + +## How it works + +* **Geometry** — each `"macro"` is a ROOT macro exporting `get_builder_hook_unchecked()`, the + same symbol `O2_CADtoTGeo.py` emits when converting CAD (STEP) files to TGeo. The macros here + are hand-written so the example is self-contained (no CAD binaries). The volumes whose names + match `"sensitiveVolumes"` are registered as sensitive. + +* **Sensitive action** — `ACYL` has none, so it uses the built-in action that records an + entrance/exit hit per track. `BDISK` points `"sensitiveMacro"` at a macro whose + `sensitiveAction()` returns the per-step callable; it is JIT-compiled at runtime via + `o2::conf::GetFromMacro` (the same mechanism as the generator / stepping hooks — no + recompilation, no ACLIC). The callable has the full `TVirtualMC` singleton and a few helpers + (`currentSensorID()`, `currentTrackID()`, `addHit()`). + +* **Persistence** — each detector is tied to a free **DetID** slot (`ITS`, `TST` here, chosen + because no real detector of that name is active). The DetID is the scarce resource: it fixes + the hit-file name (`o2sim_Hits.root`) and lets the hit merger instantiate a matching + receiver, so hits are written in parallel multi-worker mode just like for any built-in + detector. The branch keeps the detector's own name (`ACYLHit`, `BDISKHit`). + +## Adding more detectors + +Append entries to `externalDetectors.json` (each on a different free DetID slot) and list their +names in `detectorlist.json`. The mechanism is fully data-driven; nothing needs to be rebuilt. + +See also `Detectors/External` and `scripts/geometry/O2_CADtoTGeo.py`. diff --git a/run/SimExamples/External_Sensitive_Detectors/detectorlist.json b/run/SimExamples/External_Sensitive_Detectors/detectorlist.json new file mode 100644 index 0000000000000..2284e67f19748 --- /dev/null +++ b/run/SimExamples/External_Sensitive_Detectors/detectorlist.json @@ -0,0 +1,6 @@ +{ + "EXTEXAMPLE": [ + "ACYL", + "BDISK" + ] +} diff --git a/run/SimExamples/External_Sensitive_Detectors/externalDetectors.json b/run/SimExamples/External_Sensitive_Detectors/externalDetectors.json new file mode 100644 index 0000000000000..abccc209c4819 --- /dev/null +++ b/run/SimExamples/External_Sensitive_Detectors/externalDetectors.json @@ -0,0 +1,23 @@ +{ + "externalDetectors": [ + { + "name": "ACYL", + "title": "artificial barrel cylinder - built-in entrance/exit action", + "macro": "geometry_innerCylinder.macro", + "anchor": "barrel", + "detID": "ITS", + "sensitiveVolumes": ["ACYL_SENS"], + "placement": { "translation": [0, 0, 0] } + }, + { + "name": "BDISK", + "title": "artificial endcap disk - custom JITed sensitive action", + "macro": "geometry_outerDisk.macro", + "anchor": "barrel", + "detID": "TST", + "sensitiveVolumes": ["BDISK_SENS"], + "sensitiveMacro": "sensitive_action.macro", + "placement": { "translation": [0, 0, 0] } + } + ] +} diff --git a/run/SimExamples/External_Sensitive_Detectors/geometry_innerCylinder.macro b/run/SimExamples/External_Sensitive_Detectors/geometry_innerCylinder.macro new file mode 100644 index 0000000000000..78bec83f42213 --- /dev/null +++ b/run/SimExamples/External_Sensitive_Detectors/geometry_innerCylinder.macro @@ -0,0 +1,35 @@ +// Geometry builder for the "ACYL" artificial detector: a thin silicon cylindrical shell. +// +// This is a hand-written stand-in for the macros that O2_CADtoTGeo.py produces from CAD +// files. It exports the same builder-hook symbol (get_builder_hook_unchecked) that +// o2::base::buildCADVolumeFromMacro expects, so it is injected exactly like a CAD module +// through the external-geometry JSON. Media are placeholders here; remapCADMedia() +// re-registers them under O2's MaterialManager at construction time. +#include +#include +#include +#include +#include +#include +#include + +TGeoVolume* build(bool /*check*/ = true) +{ + if (!gGeoManager) { + throw std::runtime_error("gGeoManager is null when building ACYL"); + } + auto* mat = new TGeoMaterial("SiACYL", 28.0855, 14., 2.33, 9.37, 45.5); + double params[8] = {1., 0., 0., 0., 0., 0., 0., 0.}; // isvol=1, no field, defaults + auto* med = new TGeoMedium("SiACYL", 1, mat, params); + + // 2 mm thick shell, R = 20 cm, half-length 40 cm + auto* tube = new TGeoTube("ACYL_SENS", 20.0, 20.2, 40.0); + auto* sens = new TGeoVolume("ACYL_SENS", tube, med); + + auto* top = new TGeoVolumeAssembly("ACYL"); + top->AddNode(sens, 1, nullptr); + return top; +} + +std::function get_builder_hook_checked() { return []() { return build(true); }; } +std::function get_builder_hook_unchecked() { return []() { return build(false); }; } diff --git a/run/SimExamples/External_Sensitive_Detectors/geometry_outerDisk.macro b/run/SimExamples/External_Sensitive_Detectors/geometry_outerDisk.macro new file mode 100644 index 0000000000000..13907ebb94a5b --- /dev/null +++ b/run/SimExamples/External_Sensitive_Detectors/geometry_outerDisk.macro @@ -0,0 +1,35 @@ +// Geometry builder for the "BDISK" artificial detector: a thin silicon endcap disk. +// +// Hand-written stand-in for a CAD-exported macro (see geometry_innerCylinder.macro). Exports the +// same builder-hook symbol consumed by o2::base::buildCADVolumeFromMacro. Together with ACYL +// this gives two geometrically distinct artificial detectors (a barrel shell and an endcap +// disk) sharing one generic o2::ext::Hit type but writing into separate DetID slots. +#include +#include +#include +#include +#include +#include +#include +#include + +TGeoVolume* build(bool /*check*/ = true) +{ + if (!gGeoManager) { + throw std::runtime_error("gGeoManager is null when building BDISK"); + } + auto* mat = new TGeoMaterial("SiBDISK", 28.0855, 14., 2.33, 9.37, 45.5); + double params[8] = {1., 0., 0., 0., 0., 0., 0., 0.}; // isvol=1, no field, defaults + auto* med = new TGeoMedium("SiBDISK", 1, mat, params); + + // a 2 mm thick disk: inner r = 5 cm, outer r = 40 cm, placed at z = +70 cm (endcap) + auto* disk = new TGeoTube("BDISK_SENS", 5.0, 40.0, 0.1); + auto* sens = new TGeoVolume("BDISK_SENS", disk, med); + + auto* top = new TGeoVolumeAssembly("BDISK"); + top->AddNode(sens, 1, new TGeoTranslation(0., 0., 70.0)); + return top; +} + +std::function get_builder_hook_checked() { return []() { return build(true); }; } +std::function get_builder_hook_unchecked() { return []() { return build(false); }; } diff --git a/run/SimExamples/External_Sensitive_Detectors/inspect_hits.macro b/run/SimExamples/External_Sensitive_Detectors/inspect_hits.macro new file mode 100644 index 0000000000000..caebe1496b851 --- /dev/null +++ b/run/SimExamples/External_Sensitive_Detectors/inspect_hits.macro @@ -0,0 +1,50 @@ +// Inspect the hits produced by the two artificial external detectors of this example. +// +// In parallel mode (o2-sim) the hit merger writes one file per detector, named after the +// DetID slot the detector borrows: ACYL -> o2sim_HitsITS.root, BDISK -> o2sim_HitsTST.root. +// The branch inside keeps the detector's own name: "ACYLHit" / "BDISKHit". +// +// Run: root -l -b -q inspect_hits.macro +#if !defined(__CLING__) || defined(__ROOTCLING__) +#include "ExternalDetectors/Hit.h" +#include +#include +#include +#include +#endif + +void inspectOne(const char* fname, const char* branch) +{ + auto* f = TFile::Open(fname); + if (!f || f->IsZombie()) { + printf(" %-22s : file not found (%s)\n", branch, fname); + return; + } + auto* t = (TTree*)f->Get("o2sim"); + if (!t || !t->GetBranch(branch)) { + printf(" %-22s : no '%s' branch in %s\n", branch, branch, fname); + delete f; + return; + } + std::vector* hits = nullptr; + t->SetBranchAddress(branch, &hits); + long total = 0; + double rsum = 0.; + for (Long64_t e = 0; e < t->GetEntries(); ++e) { + t->GetEntry(e); + for (auto& h : *hits) { + ++total; + rsum += std::hypot(h.GetX(), h.GetY()); + } + } + printf(" %-22s : %ld hits over %lld events, mean radius %.1f cm [%s]\n", + branch, total, t->GetEntries(), total ? rsum / total : 0., fname); + delete f; +} + +void inspect_hits() +{ + printf("External sensitive detector hits:\n"); + inspectOne("o2sim_HitsITS.root", "ACYLHit"); // barrel cylinder, built-in action + inspectOne("o2sim_HitsTST.root", "BDISKHit"); // endcap disk, custom JITed action +} diff --git a/run/SimExamples/External_Sensitive_Detectors/run.sh b/run/SimExamples/External_Sensitive_Detectors/run.sh new file mode 100755 index 0000000000000..2cd94aa49c8c9 --- /dev/null +++ b/run/SimExamples/External_Sensitive_Detectors/run.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# +# Minimal example injecting two artificial *sensitive* external detectors into o2-sim. +# +# Neither detector is compiled into O2: both are described purely by data. The geometry of +# each is built at runtime from a ROOT macro (here hand-written stand-ins for what +# O2_CADtoTGeo.py produces from CAD files), and each is tied to a free DetID slot so its hits +# are written like those of any built-in detector -- including in parallel (multi-worker) mode, +# where the hit merger instantiates a matching receiver per detector. +# +# ACYL : thin silicon barrel cylinder, DetID slot ITS, built-in entrance/exit action +# BDISK : thin silicon endcap disk, DetID slot TST, custom JITed sensitive action +# +# Both share the single generic hit type o2::ext::Hit. Multiplicity is data-driven: add more +# entries (on more free DetID slots) to externalDetectors.json and detectorlist.json. + +set -x + +# run from this example's directory so the relative macro/JSON paths resolve +cd "$(dirname "$0")" + +NWORKERS=2 +EVENTS=5 + +o2-sim -j ${NWORKERS} -n ${EVENTS} -g boxgen \ + --detectorList EXTEXAMPLE:detectorlist.json \ + --extGeomFile externalDetectors.json \ + --configKeyValues 'BoxGun.number=50' \ + > sim.log 2>&1 + +# count and locate the produced hits (one file per detector / DetID slot) +root -l -b -q inspect_hits.macro diff --git a/run/SimExamples/External_Sensitive_Detectors/sensitive_action.macro b/run/SimExamples/External_Sensitive_Detectors/sensitive_action.macro new file mode 100644 index 0000000000000..29734a8aea9f9 --- /dev/null +++ b/run/SimExamples/External_Sensitive_Detectors/sensitive_action.macro @@ -0,0 +1,44 @@ +// Custom sensitive action for the "BDISK" artificial detector. +// +// The action is JIT-compiled at runtime via o2::conf::GetFromMacro (the same mechanism used +// for generator and stepping hooks) and fully replaces ExternalDetector's built-in +// entrance/exit action. It runs for every MC step inside one of the detector's sensitive +// volumes and has the full TVirtualMC singleton available, plus a few convenience helpers on +// the detector (currentSensorID(), currentTrackID(), addHit()). +// +// IMPORTANT: the return type must be spelled exactly as the typedef name passed to +// GetFromMacro ("o2::ext::ExternalDetector::SensitiveFcn"); the loader compares the function's +// return-type name textually, so std::function<...> would not match. +// +// This example records a single point-like hit whenever a charged particle enters a sensitive +// volume. +#if !defined(__CLING__) || defined(__ROOTCLING__) +#include "ExternalDetectors/ExternalDetector.h" +#include "ExternalDetectors/Hit.h" +#include +#include +#endif + +o2::ext::ExternalDetector::SensitiveFcn sensitiveAction() +{ + return [](o2::ext::ExternalDetector* det) -> bool { + auto vmc = TVirtualMC::GetMC(); + if (vmc->TrackCharge() == 0) { + return false; // ignore neutral particles + } + if (!vmc->IsTrackEntering()) { + return false; // record only the entrance point + } + const int sensor = det->currentSensorID(); + if (sensor < 0) { + return false; // not one of our sensitive volumes + } + TLorentzVector pos, mom; + vmc->TrackPosition(pos); + vmc->TrackMomentum(mom); + det->addHit(det->currentTrackID(), sensor, pos.Vect(), pos.Vect(), mom.Vect(), + mom.E(), pos.T(), 0. /*eLoss*/, o2::ext::Hit::kTrackEntering, o2::ext::Hit::kTrackEntering, + vmc->TrackPid(), vmc->TrackLength()); + return true; + }; +} diff --git a/scripts/geometry/O2_CADtoTGeo.py b/scripts/geometry/O2_CADtoTGeo.py index 3de2fd75973df..836488444c50e 100644 --- a/scripts/geometry/O2_CADtoTGeo.py +++ b/scripts/geometry/O2_CADtoTGeo.py @@ -50,11 +50,14 @@ import struct from dataclasses import dataclass from pathlib import Path as _Path -from typing import Dict, List, Optional, Tuple +from typing import Dict, List, Optional, Pattern, Tuple from OCC.Core.Bnd import Bnd_Box +from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Common from OCC.Core.BRepBndLib import brepbndlib +from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_Transform from OCC.Core.BRepMesh import BRepMesh_IncrementalMesh +from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox from OCC.Core.BRep import BRep_Tool from OCC.Core.TopLoc import TopLoc_Location from OCC.Core.TopAbs import TopAbs_REVERSED @@ -67,7 +70,7 @@ from OCC.Core.TDF import TDF_Label, TDF_LabelSequence, TDF_Tool from OCC.Core.TCollection import TCollection_AsciiString -from OCC.Core.gp import gp_Trsf +from OCC.Core.gp import gp_Pnt, gp_Trsf # volume properties for density calcs (may not be present in older pythonOCC builds) try: @@ -161,6 +164,61 @@ def detect_step_length_unit(step_path: str) -> str: return "mm" +@dataclass(frozen=True) +class ClipBox: + xmin: float + ymin: float + zmin: float + xmax: float + ymax: float + zmax: float + + @classmethod + def from_values(cls, values: List[float]) -> "ClipBox": + if len(values) != 6: + raise ValueError("--clip-box expects 6 values: xmin ymin zmin xmax ymax zmax") + xmin, ymin, zmin, xmax, ymax, zmax = (float(v) for v in values) + if not (xmin < xmax and ymin < ymax and zmin < zmax): + raise ValueError("--clip-box requires xmin Tuple[float, float, float, float, float, float]: + return (self.xmin, self.ymin, self.zmin, self.xmax, self.ymax, self.zmax) + + +@dataclass(frozen=True) +class NameFilter: + include: Tuple[Pattern[str], ...] + exclude: Tuple[Pattern[str], ...] + + @classmethod + def from_patterns(cls, include: List[str], exclude: List[str], case_sensitive: bool = False) -> "NameFilter": + flags = 0 if case_sensitive else re.IGNORECASE + return cls( + tuple(re.compile(pattern, flags) for pattern in include), + tuple(re.compile(pattern, flags) for pattern in exclude), + ) + + @property + def active(self) -> bool: + return bool(self.include or self.exclude) + + @property + def has_include(self) -> bool: + return bool(self.include) + + def _text(self, lid: str, name: str) -> str: + return f"{name} {lid}".strip() + + def matches_include(self, lid: str, name: str) -> bool: + text = self._text(lid, name) + return any(pattern.search(text) for pattern in self.include) + + def matches_exclude(self, lid: str, name: str) -> bool: + text = self._text(lid, name) + return any(pattern.search(text) for pattern in self.exclude) + + # ------------------------------- # Triangulation helpers # ------------------------------- @@ -921,6 +979,113 @@ def emit_assembly_cpp(lid: str, asm_display_name: str) -> str: return f' TGeoVolumeAssembly *asm_{safe} = new TGeoVolumeAssembly("{name}");' +# ------------------------------- +# CAD clipping helpers +# ------------------------------- + +def make_clip_box_shape(clip_box: ClipBox): + return BRepPrimAPI_MakeBox( + gp_Pnt(clip_box.xmin, clip_box.ymin, clip_box.zmin), + gp_Pnt(clip_box.xmax, clip_box.ymax, clip_box.zmax), + ).Shape() + + +def _compose_trsf(parent_to_world: gp_Trsf, local_to_parent: gp_Trsf) -> gp_Trsf: + return parent_to_world.Multiplied(local_to_parent) + + +def _shape_is_empty(shape) -> bool: + if shape is None: + return True + try: + if shape.IsNull(): + return True + except Exception: + pass + try: + for _ in TopologyExplorer(shape).faces(): + return False + return True + except Exception: + return False + + +def _transformed_bbox(shape, trsf: gp_Trsf) -> Optional[Tuple[float, float, float, float, float, float]]: + box = Bnd_Box() + brepbndlib.Add(shape, box) + try: + xmin, ymin, zmin, xmax, ymax, zmax = box.Get() + except Exception: + return None + + points = [] + for x in (xmin, xmax): + for y in (ymin, ymax): + for z in (zmin, zmax): + p = gp_Pnt(x, y, z) + p.Transform(trsf) + points.append((p.X(), p.Y(), p.Z())) + + return ( + min(p[0] for p in points), + min(p[1] for p in points), + min(p[2] for p in points), + max(p[0] for p in points), + max(p[1] for p in points), + max(p[2] for p in points), + ) + + +def _bbox_outside_clip_box(bbox: Tuple[float, float, float, float, float, float], clip_box: ClipBox) -> bool: + xmin, ymin, zmin, xmax, ymax, zmax = bbox + return ( + xmax < clip_box.xmin or xmin > clip_box.xmax or + ymax < clip_box.ymin or ymin > clip_box.ymax or + zmax < clip_box.zmin or zmin > clip_box.zmax + ) + + +def _bbox_inside_clip_box(bbox: Tuple[float, float, float, float, float, float], clip_box: ClipBox) -> bool: + xmin, ymin, zmin, xmax, ymax, zmax = bbox + return ( + xmin >= clip_box.xmin and xmax <= clip_box.xmax and + ymin >= clip_box.ymin and ymax <= clip_box.ymax and + zmin >= clip_box.zmin and zmax <= clip_box.zmax + ) + + +def _classify_shape_against_clip_box(shape, clip_box: ClipBox, local_to_world: gp_Trsf) -> Optional[str]: + world_bbox = _transformed_bbox(shape, local_to_world) + if world_bbox is None: + return None + if _bbox_outside_clip_box(world_bbox, clip_box): + return "outside" + if _bbox_inside_clip_box(world_bbox, clip_box): + return "inside" + return "overlap" + + +def clip_shape_to_box(shape, clip_box: ClipBox, clip_box_shape, local_to_world: gp_Trsf, lid: str): + clip_state = _classify_shape_against_clip_box(shape, clip_box, local_to_world) + if clip_state is None: + return None + if clip_state == "outside": + return None + if clip_state == "inside": + return shape + + local_clip = BRepBuilderAPI_Transform(clip_box_shape, local_to_world.Inverted(), True).Shape() + common = BRepAlgoAPI_Common(shape, local_clip) + common.Build() + if not common.IsDone(): + raise RuntimeError(f"Failed to clip CAD shape {lid} against --clip-box") + + clipped = common.Shape() + if _shape_is_empty(clipped): + return None + return clipped + + # ------------------------------- # Definition graph extraction # ------------------------------- @@ -939,61 +1104,188 @@ def cpp_var_for_def(lid: str) -> str: return f"asm_{safe}" if lid in assemblies else f"vol_{safe}" -def expand_definition(def_label: TDF_Label, shape_tool, meshparam=None, scale_to_cm: float = 1.0): - def_lid = label_id(def_label) - if def_lid in visited_defs: - return - visited_defs.add(def_lid) +def expand_definition( + def_label: TDF_Label, + shape_tool, + meshparam=None, + scale_to_cm: float = 1.0, + clip_box: Optional[ClipBox] = None, + clip_box_shape=None, + clip_deduplicate: str = "intact", + name_filter: Optional[NameFilter] = None, + include_subtree: bool = False, + world_trsf: Optional[gp_Trsf] = None, + occ_path: str = "r1", +) -> Optional[str]: + clip_enabled = clip_box_shape is not None + if world_trsf is None: + world_trsf = gp_Trsf() + def_lid = label_id(def_label) nm = label_name(def_label) - if nm and def_lid not in def_names: - def_names[def_lid] = nm - elif def_lid not in def_names: - def_names[def_lid] = "" + + subtree_included = include_subtree + if name_filter is not None: + if name_filter.matches_exclude(def_lid, nm): + return None + if name_filter.has_include and name_filter.matches_include(def_lid, nm): + subtree_included = True + + if clip_enabled and clip_box is not None: + try: + shape_for_clip = shape_tool.GetShape(def_label) + except Exception: + shape_for_clip = None + if shape_for_clip is not None: + clip_state = _classify_shape_against_clip_box(shape_for_clip, clip_box, world_trsf) + if clip_state == "outside": + return None + if clip_state == "inside" and clip_deduplicate == "intact": + return expand_definition( + def_label, + shape_tool, + meshparam=meshparam, + scale_to_cm=scale_to_cm, + clip_box=None, + clip_box_shape=None, + clip_deduplicate=clip_deduplicate, + name_filter=name_filter, + include_subtree=subtree_included, + ) + + def_key = f"{def_lid}@{occ_path}" if clip_enabled else def_lid + if not clip_enabled and def_lid in visited_defs: + return def_lid + if not clip_enabled: + visited_defs.add(def_lid) + + if nm and def_key not in def_names: + def_names[def_key] = nm + elif def_key not in def_names: + def_names[def_key] = "" children = TDF_LabelSequence() shape_tool.GetComponents(def_label, children) has_children = children.Length() > 0 if has_children or shape_tool.IsAssembly(def_label): - assemblies.add(def_lid) + assemblies.add(def_key) + kept_children = 0 for i in range(children.Length()): child = children.Value(i + 1) + child_occ_path = f"{occ_path}_{i + 1}" if shape_tool.IsReference(child): referred = TDF_Label() shape_tool.GetReferredShape(child, referred) - child_def_lid = label_id(referred) loc = shape_tool.GetLocation(child) trsf = loc.Transformation() - placements.append((def_lid, child_def_lid, trsf)) - - expand_definition(referred, shape_tool, meshparam=meshparam, scale_to_cm=scale_to_cm) + if clip_enabled: + child_key = expand_definition( + referred, + shape_tool, + meshparam=meshparam, + scale_to_cm=scale_to_cm, + clip_box=clip_box, + clip_box_shape=clip_box_shape, + clip_deduplicate=clip_deduplicate, + name_filter=name_filter, + include_subtree=subtree_included, + world_trsf=_compose_trsf(world_trsf, trsf), + occ_path=child_occ_path, + ) + if child_key is None: + continue + placements.append((def_key, child_key, trsf)) + else: + child_key = expand_definition( + referred, + shape_tool, + meshparam=meshparam, + scale_to_cm=scale_to_cm, + clip_deduplicate=clip_deduplicate, + name_filter=name_filter, + include_subtree=subtree_included, + ) + if child_key is None: + continue + placements.append((def_key, child_key, trsf)) else: - child_def_lid = label_id(child) - placements.append((def_lid, child_def_lid, gp_Trsf())) - expand_definition(child, shape_tool, meshparam=meshparam, scale_to_cm=scale_to_cm) - return + trsf = gp_Trsf() + if clip_enabled: + child_key = expand_definition( + child, + shape_tool, + meshparam=meshparam, + scale_to_cm=scale_to_cm, + clip_box=clip_box, + clip_box_shape=clip_box_shape, + clip_deduplicate=clip_deduplicate, + name_filter=name_filter, + include_subtree=subtree_included, + world_trsf=world_trsf, + occ_path=child_occ_path, + ) + if child_key is None: + continue + placements.append((def_key, child_key, trsf)) + else: + child_key = expand_definition( + child, + shape_tool, + meshparam=meshparam, + scale_to_cm=scale_to_cm, + clip_deduplicate=clip_deduplicate, + name_filter=name_filter, + include_subtree=subtree_included, + ) + if child_key is None: + continue + placements.append((def_key, child_key, trsf)) + kept_children += 1 + + if (clip_enabled or (name_filter is not None and name_filter.has_include)) and kept_children == 0: + assemblies.discard(def_key) + return None + return def_key if shape_tool.IsSimpleShape(def_label): - if def_lid not in logical_volumes: + if name_filter is not None and name_filter.has_include and not subtree_included: + return None + + if def_key not in logical_volumes: shape = shape_tool.GetShape(def_label) # store volume (for density estimation) try: - def_volumes_cm3[def_lid] = volume_cm3_of_shape(shape, scale_to_cm=scale_to_cm) + volume_cm3 = volume_cm3_of_shape(shape, scale_to_cm=scale_to_cm) except Exception: - def_volumes_cm3[def_lid] = 0.0 + volume_cm3 = 0.0 + + if clip_enabled: + shape = clip_shape_to_box(shape, clip_box, clip_box_shape, world_trsf, def_lid) + if shape is None: + return None + + def_volumes_cm3[def_key] = volume_cm3 do_meshing = (meshparam is not None) and meshparam.get("do_meshing", None) is True - logical_volumes[def_lid] = triangulate_CAD_solid(shape, meshparam=meshparam, scale_to_cm=scale_to_cm) if do_meshing else triangulate_asbbox(shape, scale_to_cm=scale_to_cm) - return + logical_volumes[def_key] = triangulate_CAD_solid(shape, meshparam=meshparam, scale_to_cm=scale_to_cm) if do_meshing else triangulate_asbbox(shape, scale_to_cm=scale_to_cm) + return def_key - assemblies.add(def_lid) + assemblies.add(def_key) + return def_key -def extract_graph(step_path: str, meshparam=None, scale_to_cm: float = 1.0): +def extract_graph( + step_path: str, + meshparam=None, + scale_to_cm: float = 1.0, + clip_box: Optional[ClipBox] = None, + clip_deduplicate: str = "intact", + name_filter: Optional[NameFilter] = None, +): global logical_volumes, def_names, def_volumes_cm3, assemblies, placements, top_defs, visited_defs logical_volumes = {} def_names = {} @@ -1004,20 +1296,42 @@ def extract_graph(step_path: str, meshparam=None, scale_to_cm: float = 1.0): visited_defs = set() doc, shape_tool = load_step_with_xcaf(step_path) + clip_box_shape = make_clip_box_shape(clip_box) if clip_box is not None else None roots = TDF_LabelSequence() shape_tool.GetFreeShapes(roots) for i in range(roots.Length()): root = roots.Value(i + 1) + root_occ_path = f"r{i + 1}" if shape_tool.IsReference(root): ref = TDF_Label() shape_tool.GetReferredShape(root, ref) - top_defs.add(label_id(ref)) - expand_definition(ref, shape_tool, meshparam=meshparam, scale_to_cm=scale_to_cm) + top = expand_definition( + ref, + shape_tool, + meshparam=meshparam, + scale_to_cm=scale_to_cm, + clip_box=clip_box, + clip_box_shape=clip_box_shape, + clip_deduplicate=clip_deduplicate, + name_filter=name_filter, + occ_path=root_occ_path, + ) else: - top_defs.add(label_id(root)) - expand_definition(root, shape_tool, meshparam=meshparam, scale_to_cm=scale_to_cm) + top = expand_definition( + root, + shape_tool, + meshparam=meshparam, + scale_to_cm=scale_to_cm, + clip_box=clip_box, + clip_box_shape=clip_box_shape, + clip_deduplicate=clip_deduplicate, + name_filter=name_filter, + occ_path=root_occ_path, + ) + if top is not None: + top_defs.add(top) return doc, shape_tool @@ -1074,6 +1388,9 @@ def emit_root_macro( out_folder: _Path, meshparam=None, step_unit: str = "auto", + clip_box: Optional[ClipBox] = None, + clip_deduplicate: str = "intact", + name_filter: Optional[NameFilter] = None, materials_csv: Optional[str] = None, bom_mass_unit: str = "kg", g4_nist_json: Optional[str] = None, @@ -1087,7 +1404,21 @@ def emit_root_macro( scale_to_cm = step_unit_scale_to_cm(step_unit) print(f"Using overridden STEP length unit: {step_unit} (scale to cm = {scale_to_cm})") - extract_graph(step_path, meshparam=meshparam, scale_to_cm=scale_to_cm) + if clip_box is not None: + print(f"Clipping CAD geometry to STEP-coordinate bounding box: {clip_box.as_tuple()}") + print(f"Clip deduplication mode: {clip_deduplicate}") + + if name_filter is not None and name_filter.active: + print(f"CAD name filters: {len(name_filter.include)} include regex(es), {len(name_filter.exclude)} exclude regex(es)") + + extract_graph( + step_path, + meshparam=meshparam, + scale_to_cm=scale_to_cm, + clip_box=clip_box, + clip_deduplicate=clip_deduplicate, + name_filter=name_filter, + ) out_folder = out_folder.expanduser().resolve() out_folder.mkdir(parents=True, exist_ok=True) @@ -1278,6 +1609,11 @@ def main(): ap.add_argument("--print-tree", action="store_true", help="Just prints the geometry tree") ap.add_argument("--mesh-prec", default=0.1, help="meshing precision. lower --> slower") ap.add_argument("--step-unit", default="auto", choices=["auto", "mm", "cm", "m", "in", "ft"], help="STEP length unit override (default: auto-detect); TGeo expects cm") + ap.add_argument("--clip-box", nargs=6, type=float, metavar=("XMIN", "YMIN", "ZMIN", "XMAX", "YMAX", "ZMAX"), default=None, help="Clip CAD geometry to this axis-aligned bounding box before meshing (coordinates in STEP file units, before conversion to cm)") + ap.add_argument("--clip-deduplicate", default="intact", choices=["none", "intact"], help="When clipping, reuse original logical definitions for subtrees fully inside the clip box (default: intact); use 'none' for one volume per surviving occurrence") + ap.add_argument("--include-name", action="append", default=[], help="Only convert CAD labels whose XCAF name or label entry matches this regex; may be repeated. Matching an assembly includes its subtree.") + ap.add_argument("--exclude-name", action="append", default=[], help="Skip CAD labels/subtrees whose XCAF name or label entry matches this regex; may be repeated.") + ap.add_argument("--name-filter-case-sensitive", action="store_true", help="Make --include-name/--exclude-name matching case-sensitive (default: case-insensitive)") # NEW: BOM / material support ap.add_argument("--materials-csv", default=None, help="BOM CSV file providing material + mass per part (optional)") @@ -1304,6 +1640,24 @@ def main(): if args.out_path is not None: out_folder = _Path(args.out_path) + clip_box = None + if args.clip_box is not None: + try: + clip_box = ClipBox.from_values(args.clip_box) + except ValueError as exc: + ap.error(str(exc)) + + name_filter = None + if args.include_name or args.exclude_name: + try: + name_filter = NameFilter.from_patterns( + args.include_name, + args.exclude_name, + case_sensitive=args.name_filter_case_sensitive, + ) + except re.error as exc: + ap.error(f"Invalid CAD name filter regex: {exc}") + meshparam = {"do_meshing": args.mesh, "lin_defl": args.mesh_prec, "ang_defl": args.mesh_prec} @@ -1325,6 +1679,9 @@ def main(): out_folder, meshparam=meshparam, step_unit=args.step_unit, + clip_box=clip_box, + clip_deduplicate=args.clip_deduplicate, + name_filter=name_filter, materials_csv=args.materials_csv, bom_mass_unit=args.bom_mass_unit, g4_nist_json=args.g4_nist_json, diff --git a/scripts/geometry/README.md b/scripts/geometry/README.md index 4fb2d1ec610d4..a59da13c12716 100644 --- a/scripts/geometry/README.md +++ b/scripts/geometry/README.md @@ -1,27 +1,283 @@ -This is the tool O2_CADtoTGeo.py which translates from geometries in STEP format (CAD export) to -TGeo. +# CAD-to-TGeo geometry import -To use the tool, setup a conda environment with python-occ core installed. -The following should work on standard linux x86: +`O2_CADtoTGeo.py` converts CAD geometries exported as STEP files into ROOT TGeo geometry. +The converter emits a small ROOT macro plus compact binary facet payloads. The generated +macro can be loaded directly in ROOT, or injected into `o2-sim` as an external passive +module or as a sensitive external detector. +The current integration path is data-driven: the CAD geometry is converted once, then a +JSON file tells `o2-sim` which generated macro to load, where to anchor it in the existing +geometry, and, for sensitive detectors, which volumes or media should produce hits. + +## Software setup + +The preferred setup is the normal ALICE software environment. The `pythonOCC` package pulls +in OpenCascade and the Python bindings needed by the converter: + +```bash +alienv enter O2sim/latest,pythonOCC/latest +python PATH_TO_ALICEO2_SOURCES/scripts/geometry/O2_CADtoTGeo.py --help ``` -# -) download miniconda into $HOME/miniconda (if not already done) -if [ ! -d $HOME/miniconda ]; then - curl -fsSL https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-aarch64.sh -o miniconda.sh - bash miniconda.sh -b -p $HOME/miniconda -fi -# -) source conda into the environment (in every shell you want to use this) -source $HOME/miniconda/etc/profile.d/conda.sh +If you are working from a local O2 checkout, `PATH_TO_ALICEO2_SOURCES` is the directory that +contains this `scripts/geometry` folder. + +For standalone studies outside the ALICE software stack, a conda environment with +`pythonocc-core` can also be used: -# -) Create an OCC environment (for OpenCacade) +```bash conda create -n occ python=3.10 -y conda activate occ - -# 3) Install OpenCascade Python bindings conda install -c conda-forge pythonocc-core -y -# 4) Run the tool, e.g. -conda activate occ python PATH_TO_ALICEO2_SOURCES/scripts/geometry/O2_CADtoTGeo.py --help -``` \ No newline at end of file +``` + +## Convert a STEP file + +For a quick, robust geometry preview, convert leaves to bounding boxes: + +```bash +mkdir -p cad_out/mydet +python PATH_TO_ALICEO2_SOURCES/scripts/geometry/O2_CADtoTGeo.py \ + my_detector.step \ + --output-folder cad_out/mydet \ + -o geom.C \ + --step-unit auto +``` + +For a more detailed faceted representation, enable meshing: + +```bash +python PATH_TO_ALICEO2_SOURCES/scripts/geometry/O2_CADtoTGeo.py \ + my_detector.step \ + --output-folder cad_out/mydet \ + -o geom.C \ + --mesh \ + --mesh-prec 0.05 \ + --step-unit auto +``` + +The output folder contains: + +- `geom.C`, a ROOT macro exporting `get_builder_hook_unchecked()` +- `facets_*.bin`, one compact triangle payload per leaf logical volume + +The generated macro is the file referenced from the external-geometry JSON examples below. +The macro and its facet binaries should stay together, because the macro loads the facet files +relative to its own location. + +## Restrict the converted region with a clip box + +Large CAD assemblies often contain far more than the region of interest. The `--clip-box` +option restricts the conversion to an axis-aligned bounding box, so only the geometry inside +(or overlapping) that box is written out: + +```bash +python PATH_TO_ALICEO2_SOURCES/scripts/geometry/O2_CADtoTGeo.py \ + my_detector.step \ + --output-folder cad_out/mydet \ + -o geom.C \ + --mesh \ + --clip-box XMIN YMIN ZMIN XMAX YMAX ZMAX +``` + +Notes on the coordinates: + +- The six values are `xmin ymin zmin xmax ymax zmax` and must satisfy `xmin < xmax`, + `ymin < ymax`, and `zmin < zmax`. +- Coordinates are given in the **STEP file units** (before conversion to cm), and are applied + in the global/world coordinate system of the assembly. + +Each solid is classified against the box before meshing: + +- Solids fully outside the box are dropped. +- Solids fully inside the box are kept unchanged. +- Solids straddling the box boundary are cut against it (a boolean intersection), so only the + part inside the box is meshed. + +Assemblies that end up with no surviving children are removed from the output tree. + +### Deduplication mode + +The `--clip-deduplicate` option controls how subtrees that fall entirely inside the box are +emitted: + +- `intact` (default): subtrees fully inside the box reuse their original shared logical + definitions, keeping the output compact. +- `none`: every surviving occurrence becomes its own volume, which is useful when you need a + flat, per-instance representation. + +```bash +python PATH_TO_ALICEO2_SOURCES/scripts/geometry/O2_CADtoTGeo.py \ + my_detector.step \ + --output-folder cad_out/mydet \ + -o geom.C \ + --mesh \ + --clip-box -50 -50 -20 50 50 20 \ + --clip-deduplicate none +``` + +Clipping can be combined with the name-based selection options (`--include-name` / +`--exclude-name`) to further narrow down which parts are converted. + +## Optional material mapping + +The converter can use a BOM CSV to assign materials and, when part masses and CAD volumes are +available, derive effective densities. A Geant4 NIST material JSON dump enables richer material +and tracking-length information: + +```bash +python PATH_TO_ALICEO2_SOURCES/scripts/geometry/O2_CADtoTGeo.py \ + my_detector.step \ + --output-folder cad_out/mydet \ + -o geom.C \ + --mesh \ + --materials-csv detector_bom.csv \ + --bom-mass-unit kg \ + --g4-nist-json g4_nist_materials.json +``` + +The expected BOM rows are mechanical part rows of the form: + +```text +CAD,Mechanical/Part,,,,,,... +``` + +Material names are matched to the NIST database when possible. If a match is ambiguous or not +available, the generated macro falls back to a simple material and leaves comments in `geom.C` +for follow-up. + +## Inject passive CAD geometry into `o2-sim` + +Passive external modules are configured under an `externalModules` array. Each entry needs a +module name, the generated macro, and an anchor volume already present in the O2 geometry. An +optional placement can translate and rotate the imported geometry inside the anchor volume: + +```json +{ + "externalModules": [ + { + "name": "IRIS", + "title": "IRIS support from CAD", + "macro": "cad_out/iris/geom.C", + "anchor": "barrel", + "placement": { + "translation": [0.0, 0.0, 0.0], + "rotation_deg": [0.0, 0.0, 15.0] + } + } + ] +} +``` + +The module is only added when its `name` is present in the active detector/module list. One +way to make such a list is a detector-list JSON file: + +```json +{ + "EXTCAD": ["IRIS"] +} +``` + +Run `o2-sim` with both files: + +```bash +o2-sim -n 1 -g boxgen \ + --detectorList EXTCAD:detectorlist.json \ + --extGeomFile externalGeometry.json +``` + +Multiple passive modules can be listed in the same file. The CAD macro loader JIT-compiles each +macro into a unique namespace, so several `O2_CADtoTGeo.py` outputs can coexist even though they +export the same builder-hook symbol names. + +## Inject sensitive external CAD detectors + +Sensitive external detectors are configured under an `externalDetectors` array. They use the +same generated geometry macro, but additionally select sensitive volumes or media and bind the +detector to a free O2 `DetID` slot. All such detectors are instances of +`o2::ext::ExternalDetector` and write the generic `o2::ext::Hit` format. + +```json +{ + "externalDetectors": [ + { + "name": "ECYL", + "title": "External silicon cylinder", + "macro": "cad_out/ecyl/geom.C", + "anchor": "barrel", + "detID": "ITS", + "sensitiveVolumes": ["ECYL_SENSOR"], + "placement": { "translation": [0.0, 0.0, 0.0] } + }, + { + "name": "EDISK", + "title": "External endcap disk with custom action", + "macro": "cad_out/edisk/geom.C", + "anchor": "barrel", + "detID": "TST", + "sensitiveMedia": ["Silicon"], + "sensitiveMacro": "sensitive_action.macro", + "sensitiveFunction": "sensitiveAction()" + } + ] +} +``` + +Selection rules: + +- `sensitiveVolumes` matches substrings of TGeo volume names. +- `sensitiveMedia` matches substrings of TGeo medium names. +- At least one of the two arrays must be non-empty. + +The `detID` determines the hit-file identity, for example `o2sim_HitsITS.root` or +`o2sim_HitsTST.root`. Choose a DetID that is not already occupied by an active built-in detector. +The branch name keeps the external detector name, for example `ECYLHit` or `EDISKHit`. + +If no `sensitiveMacro` is provided, the built-in action records a charged-track entrance/exit hit. +With a custom action, the macro is JIT-compiled at runtime and must return an +`o2::ext::ExternalDetector::SensitiveFcn`. The action can query `TVirtualMC::GetMC()` and use +helpers such as `currentSensorID()`, `currentTrackID()`, and `addHit()`. + +Run the detectors just like passive modules, with their names in the detector-list JSON: + +```json +{ + "EXTCAD": ["ECYL", "EDISK"] +} +``` + +```bash +o2-sim -j 2 -n 5 -g boxgen \ + --detectorList EXTCAD:detectorlist.json \ + --extGeomFile externalGeometry.json \ + --configKeyValues 'BoxGun.number=50' +``` + +In parallel mode, the hit merger reads the same `--extGeomFile`, registers the configured active +external detectors, and persists their generic external hits like built-in detector hits. + +## Complete runnable example + +A self-contained example is available in: + +```bash +run/SimExamples/External_Sensitive_Detectors +``` + +It defines two artificial sensitive detectors entirely from data: + +- `ACYL`, a silicon barrel cylinder using the built-in entrance/exit action +- `BDISK`, a silicon endcap disk using a custom JITed sensitive action + +The example uses hand-written geometry macros that mimic `O2_CADtoTGeo.py` output, so it does not +require CAD input files. Run it from its directory: + +```bash +cd run/SimExamples/External_Sensitive_Detectors +./run.sh +``` + +The script transports a few box-generator events and prints the hit counts for the produced +external-detector branches. \ No newline at end of file diff --git a/scripts/geometry/TODO.md b/scripts/geometry/TODO.md new file mode 100644 index 0000000000000..8add645e06357 --- /dev/null +++ b/scripts/geometry/TODO.md @@ -0,0 +1,4 @@ +- implement a BVHSurface solid as an exact representation of a CAD solid +- complete geometry configurable as JSON --> even the world volume ? + + \ No newline at end of file