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/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/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/Detectors/Base/CMakeLists.txt b/Detectors/Base/CMakeLists.txt index 83a9193274e4f..74d2d02a9246c 100644 --- a/Detectors/Base/CMakeLists.txt +++ b/Detectors/Base/CMakeLists.txt @@ -92,6 +92,7 @@ if(BUILD_SIMULATION) endif() install(FILES test/buildMatBudLUT.C + test/compareMatBudLUT.C test/extractLUTLayers.C test/rescaleLUT.C DESTINATION share/macro/) @@ -100,6 +101,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/GeometryManager.h b/Detectors/Base/include/DetectorsBase/GeometryManager.h index ad1d77b10f49a..5e296a4045984 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 { @@ -96,14 +97,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); diff --git a/Detectors/Base/include/DetectorsBase/MatLayerCyl.h b/Detectors/Base/include/DetectorsBase/MatLayerCyl.h index e63de51e0a6ca..26de279468477 100644 --- a/Detectors/Base/include/DetectorsBase/MatLayerCyl.h +++ b/Detectors/Base/include/DetectorsBase/MatLayerCyl.h @@ -25,6 +25,8 @@ #include "GPUCommonMath.h" #include "DetectorsBase/MatCell.h" +class TGeoNavigator; + namespace o2 { namespace base @@ -66,7 +68,7 @@ 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 ip, int iz, int ntrPerCell, TGeoNavigator* nav = nullptr); void print(bool data = false) const; #endif // !GPUCA_ALIGPUCODE @@ -107,7 +109,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..4408261dc740a 100644 --- a/Detectors/Base/include/DetectorsBase/MatLayerCylSet.h +++ b/Detectors/Base/include/DetectorsBase/MatLayerCylSet.h @@ -73,7 +73,10 @@ 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. nThreads > 1 fills the cells in parallel (one TGeoNavigator + /// per thread); nThreads < 0 takes the count from the NTHREADS_MATBUD environment variable. + void populateFromTGeo(int ntrPerCel = 10, int nThreads = -1); + 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/src/GeometryManager.cxx b/Detectors/Base/src/GeometryManager.cxx index a067767752a69..8aaf902aa95c5 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 @@ -398,7 +399,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 +416,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 +429,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 +451,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 +470,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 +484,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; diff --git a/Detectors/Base/src/MatLayerCyl.cxx b/Detectors/Base/src/MatLayerCyl.cxx index 2efe60235b895..dee179a84ca06 100644 --- a/Detectors/Base/src/MatLayerCyl.cxx +++ b/Detectors/Base/src/MatLayerCyl.cxx @@ -123,7 +123,7 @@ void MatLayerCyl::populateFromTGeo(int ntrPerCell) } //________________________________________________________________________________ -void MatLayerCyl::populateFromTGeo(int ip, int iz, int ntrPerCell) +void MatLayerCyl::populateFromTGeo(int ip, int iz, int ntrPerCell, TGeoNavigator* nav) { /// populate cell with info extracted from TGeometry, using ntrPerCell test tracks per cell @@ -136,7 +136,7 @@ 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); + auto bud = o2::base::GeometryManager::meanMaterialBudget(rMin * cs, rMin * sn, zs - dzt, rMax * cs, rMax * sn, zs + dzt, nav); 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..b85e3042cc379 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,9 +78,26 @@ 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) +{ + ///< populate layers, using ntrPerCell test tracks per cell. + ///< nThreads < 0 takes the number of threads from the NTHREADS_MATBUD environment variable. assert(mConstructionMask == InProgress); int nlr = getNLayers(); @@ -83,12 +109,86 @@ 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 tFillStart = Clock::now(); + for (int i = 0; i < nlr; i++) { + get()->mLayers[i].populateFromTGeo(ntrPerCell); + } + const auto tFillEnd = Clock::now(); + finalizeStructures(); + LOG(info) << "LUT fill: 1 thread, 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(); + + // 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(); }); + + const auto tFillStart = Clock::now(); + { + tbb::global_control threadControl(tbb::global_control::max_allowed_parallelism, nThreads); + tbb::parallel_for(tbb::blocked_range(0, totalCells), + [this, ntrPerCell, &layerOffsets, &threadNavigators](const tbb::blocked_range& range) { + TGeoNavigator* nav = threadNavigators.local(); + 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); + } + }); } + + const auto tFillEnd = Clock::now(); + + for (TGeoNavigator* nav : threadNavigators) { + gGeoManager->RemoveNavigator(nav); + } + 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 +448,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/test/README.md b/Detectors/Base/test/README.md index f5f9fd4c04b29..97e8c7f569fd1 100644 --- a/Detectors/Base/test/README.md +++ b/Detectors/Base/test/README.md @@ -13,6 +13,16 @@ 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 +5th 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. + 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..44019198685f8 100644 --- a/Detectors/Base/test/buildMatBudLUT.C +++ b/Detectors/Base/test/buildMatBudLUT.C @@ -27,7 +27,10 @@ o2::base::MatLayerCylSet mbLUT; bool testMBLUT(const std::string& lutFile = "matbud.root"); -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. +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); struct LrData { float rMin = 0.f; @@ -42,7 +45,8 @@ 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) { auto geomName = o2::base::NameConf::getGeomFileName(geomNamePrefix); if (gSystem->AccessPathName(geomName.c_str())) { // if needed, create geometry @@ -67,7 +71,7 @@ bool buildMatBudLUT(int nTst, int maxLr, const std::string& outFile, const std:: } TStopwatch sw; - mbLUT.populateFromTGeo(nTst); + mbLUT.populateFromTGeo(nTst, nThreads); mbLUT.optimizePhiSlices(); // move to populateFromTGeo mbLUT.flatten(); // move to populateFromTGeo diff --git a/Detectors/Base/test/compareMatBudLUT.C b/Detectors/Base/test/compareMatBudLUT.C new file mode 100644 index 0000000000000..7abaa9bd7182a --- /dev/null +++ b/Detectors/Base/test/compareMatBudLUT.C @@ -0,0 +1,93 @@ +// 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: +/// +/// root -b -q 'compareMatBudLUT.C("matbud_serial.root","matbud_parallel.root")' + +#if !defined(__CLING__) || defined(__ROOTCLING__) +#include "DetectorsBase/MatLayerCylSet.h" +#include "GPUCommonLogger.h" +#include +#include +#endif + +/// Returns true if the two LUTs agree everywhere within tol (relative). +bool compareMatBudLUT(const std::string& fileA = "matbud_serial.root", + const std::string& fileB = "matbud_parallel.root", + float tol = 0.f) +{ + 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.; + + 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; + } + 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); + + if (rRho > tol || rX > tol) { + if (nBad < 10) { + printf("Lr %3d iz %4d ip %4d : rho %.9g vs %.9g (rel %.3g) | x2x0 %.9g vs %.9g (rel %.3g)\n", + il, iz, ip, ca.meanRho, cb.meanRho, rRho, ca.meanX2X0, cb.meanX2X0, rX); + } + nBad++; + } + } + } + } + + printf("Compared %zu cells over %d layers\n", nCells, lutA->getNLayers()); + printf("Max relative difference: meanRho %.3g, meanX2X0 %.3g (tolerance %.3g)\n", maxRelRho, maxRelX2X0, tol); + if (nBad) { + LOG(error) << nBad << " cells differ beyond tolerance"; + return false; + } + LOG(info) << "LUTs agree"; + return true; +} 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/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/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/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/TPC/base/include/TPCBase/ParameterGas.h b/Detectors/TPC/base/include/TPCBase/ParameterGas.h index 210d8dbd14867..e5e3ab68c3eb9 100644 --- a/Detectors/TPC/base/include/TPCBase/ParameterGas.h +++ b/Detectors/TPC/base/include/TPCBase/ParameterGas.h @@ -41,6 +41,7 @@ struct ParameterGas : public o2::conf::ConfigurableParamHelper { float Pressure = 1013.25f; ///< Pressure [mbar] float Temperature = 20.0f; ///< Temperature [°C] float BetheBlochParam[5] = {0.820172e-1f, 9.94795f, 8.97292e-05f, 2.05873f, 1.65272f}; ///< Parametrization of Bethe-Bloch + int MaxElePerStep = 300; ///< maximum number of electron allowed per step, default is 300 ~10keV O2ParamDef(ParameterGas, "TPCGasParam"); }; 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/simulation/include/TPCSimulation/Point.h b/Detectors/TPC/simulation/include/TPCSimulation/Point.h index dd477d1d20c33..1ce7fdc9f1a35 100644 --- a/Detectors/TPC/simulation/include/TPCSimulation/Point.h +++ b/Detectors/TPC/simulation/include/TPCSimulation/Point.h @@ -117,7 +117,7 @@ class HitGroup : public o2::BaseHit ~HitGroup() = default; - void addHit(float x, float y, float z, float time, short e) + void addHit(float x, float y, float z, float time, float e) { #ifdef HIT_AOS mHits.emplace_back(x, y, z, time, e); diff --git a/Detectors/TPC/simulation/src/Detector.cxx b/Detectors/TPC/simulation/src/Detector.cxx index 1a7c0fc25802b..8c9c45b6c13a8 100644 --- a/Detectors/TPC/simulation/src/Detector.cxx +++ b/Detectors/TPC/simulation/src/Detector.cxx @@ -226,7 +226,7 @@ Bool_t Detector::ProcessHits(FairVolume* vol) const double rndm = fMC->GetRandom()->Rndm(); const double eDep = TMath::Power((kMax - kMin) * rndm + kMin, oneOverAlpha_p1); int nel_step = static_cast(((eDep - eMin) / wIon) + 1); - nel_step = TMath::Min(nel_step, 300); // 300 electrons corresponds to 10 keV + nel_step = TMath::Min(nel_step, gasParam.MaxElePerStep); // 300 electrons corresponds to 10 keV numberOfElectrons += nel_step; } 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..dcb53c9f80ff8 100644 --- a/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx +++ b/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx @@ -1335,12 +1335,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 @@ -1395,7 +1400,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 +1455,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] diff --git a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/CMakeLists.txt b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/CMakeLists.txt index 1dfcb7a22f725..9cc7222d413e7 100644 --- a/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/reconstruction/CMakeLists.txt @@ -16,7 +16,6 @@ endif() o2_add_library(ALICE3GlobalReconstruction TARGETVARNAME targetName SOURCES src/TimeFrame.cxx - $<$:src/TrackerACTS.cxx> PUBLIC_LINK_LIBRARIES O2::ITStracking O2::GPUCommon 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..7d3dbdc1bd1dc 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/base/include/IOTOFBase/IOTOFBaseParam.h +++ b/Detectors/Upgrades/ALICE3/IOTOF/base/include/IOTOFBase/IOTOFBaseParam.h @@ -41,7 +41,7 @@ struct ChipSpecifics { struct ITOFChipSpecifics : ChipSpecifics { ITOFChipSpecifics() { - NCols = 258; + NCols = 129; NRows = 271; PitchCol = 250.00e-4; PitchRow = 100.00e-4; @@ -53,11 +53,12 @@ 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; SensorLayerThicknessEff = 50.e-4; SensorLayerThickness = 50.e-4; } 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..fbff3330865db 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/simulation/include/IOTOFSimulation/DPLDigitizerParam.h +++ b/Detectors/Upgrades/ALICE3/IOTOF/simulation/include/IOTOFSimulation/DPLDigitizerParam.h @@ -27,10 +27,11 @@ struct DPLDigitizerParam : public o2::conf::ConfigurableParamHelper @@ -30,7 +31,6 @@ namespace o2::iotof { o2::iotof::Segmentation* Digitizer::sSegmentation = nullptr; - //_______________________________________________________________________ void Digitizer::init() { @@ -100,7 +100,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,6 +110,8 @@ 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) { @@ -128,25 +130,126 @@ 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.; + } + } + } } //_______________________________________________________________________ @@ -200,10 +303,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() < mChargeThreshold) { + 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/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/Digitizer.h b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Digitizer.h index 5910fc98134aa..0f857c6a0cfe2 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Digitizer.h +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Digitizer.h @@ -65,7 +65,7 @@ class Digitizer mROFrameMin = 0; mROFrameMax = 0; mNewROFrame = 0; - mIsBeforeFirstRO = false; + mROFsWrtFirstRO = 0; mExtraBuff.clear(); } @@ -139,7 +139,7 @@ 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) diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/Digitizer.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/Digitizer.cxx index 890c272fefbc2..4b85238dc8e24 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/Digitizer.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/Digitizer.cxx @@ -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; @@ -283,7 +282,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; diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx index e3855dbde6535..fb436cd473021 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx @@ -922,7 +922,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/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/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/Detectors/ZDC/raw/src/ZDCRawLinkDef.h b/Detectors/ZDC/raw/src/ZDCRawLinkDef.h deleted file mode 100644 index c85dd2d378ccb..0000000000000 --- a/Detectors/ZDC/raw/src/ZDCRawLinkDef.h +++ /dev/null @@ -1,18 +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. - -#ifdef __CLING__ - -#pragma link off all globals; -#pragma link off all classes; -#pragma link off all functions; - -#endif 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/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..1e483d017cc88 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,11 +304,19 @@ 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); - +#if (FAIRMQ_VERSION_DEC >= 111000) + if constexpr (!is_enumeration) { + groupingTable.setPointerReconstructor(pointerReconstructor); + } +#endif constexpr const int numElements = nested_brace_constructible_size>() / 10; // set filtered tables for partitions with grouping @@ -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 { @@ -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/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/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/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/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 40f7a34e699c9..842576621c39c 100644 --- a/GPU/GPUTracking/Definitions/GPUSettingsList.h +++ b/GPU/GPUTracking/Definitions/GPUSettingsList.h @@ -113,13 +113,13 @@ AddOptionRTC(maxTimeBinAboveThresholdIn1000Bin, uint16_t, 500, "", 0, "Except pa AddOptionRTC(maxConsecTimeBinAboveThreshold, uint16_t, 200, "", 0, "Except pad from cluster finding if number of consecutive charges in a fragment is above this baseline (disable = 0)") AddOptionRTC(noisyPadSaturationThreshold, uint16_t, 700, "", 0, "Threshold where a timebin is considered saturated, disabling the noisy pad check for that pad") AddOptionRTC(hipTailFilter, uint8_t, 0, "", 0, "Enable Highly Ionising Particle tail filter in CheckPadBaseline (0 = disable, 1 = filter tails)") -AddOptionRTC(hipTailFilterMinimum, uint16_t, 1024, "", 0, "Thread signal above this minimum as saturated") +AddOptionRTC(hipTailFilterMinimum, uint16_t, 1023, "", 0, "Thread signal above this minimum as saturated") AddOptionRTC(hipTailFilterThreshold, uint16_t, 100, "", 0, "Threshold that must be exceeded for a timebin to be counted towards Highly Ionising Particle tail") AddOptionRTC(hipTailFilterAlpha, float, 0.5f, "", 0, "Smoothing factor for the exponential Highly Ionising Particle tail filter") 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 462b7798ce337..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); @@ -1144,16 +1144,12 @@ int32_t GPUChainTracking::RunTPCClusterizer(bool synchronizeOutput) // TODO Add some warning when re enabling pad filter with this flag, so it's not just silently enabled when disabling was requested checkForNoisyPads |= rec()->GetParam().rec.tpc.hipTailFilter; - if (rec()->GetParam().rec.tpc.hipTailFilter && !doGPU) { - GPUError("HIP tail filter enabled, but this is currently not supported on CPU"); - } - if (checkForNoisyPads) { if (rec()->GetParam().rec.tpc.hipTailFilter) { runKernel({GetGridAutoStep(lane, RecoStep::TPCClusterFinding)}, clustererShadow.mPhipTailsByRow, GPUTPCGeometry::NROWS * sizeof(*clustererShadow.mPhipTailsByRow) * GPUTPCCFHIPClusterizer::MaxHIPTailsPerRow); runKernel({GetGridAutoStep(lane, RecoStep::TPCClusterFinding)}, clustererShadow.mPnHIPTails, GPUTPCGeometry::NROWS * sizeof(*clustererShadow.mPnHIPTails)); } - const int32_t nBlocks = GPUTPCCFCheckPadBaseline::GetNBlocks(doGPU); + const int32_t nBlocks = GPUTPCGeometry::NROWS; runKernel({GetGridBlk(nBlocks, lane), {iSector}}); getKernelTimer(RecoStep::TPCClusterFinding, iSector, TPC_REAL_PADS_IN_SECTOR * fragment.lengthWithoutOverlap() * sizeof(PackedCharge), false); @@ -1475,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/TPCClusterFinder/GPUTPCCFCheckPadBaseline.cxx b/GPU/GPUTracking/TPCClusterFinder/GPUTPCCFCheckPadBaseline.cxx index 5a5fb478fe065..7470f86877490 100644 --- a/GPU/GPUTracking/TPCClusterFinder/GPUTPCCFCheckPadBaseline.cxx +++ b/GPU/GPUTracking/TPCClusterFinder/GPUTPCCFCheckPadBaseline.cxx @@ -22,6 +22,7 @@ #ifndef GPUCA_GPUCODE #include "MCLabelAccumulator.h" #include "utils/VcShim.h" +#include #endif #if 0 @@ -344,7 +345,7 @@ GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineGPU(int32_t nBlocks, int32_t GPUbarrier(); - } // if (hipTriggerFound) + } // if (hasHIPTrigger) } // for (uint16_t t = firstTB; t < lastTB; t += NumOfCachedTBs) @@ -374,60 +375,264 @@ GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineGPU(int32_t nBlocks, int32_t GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineCPU(int32_t nBlocks, int32_t nThreads, int32_t iBlock, int32_t iThread, GPUSharedMemory& smem, processorType& clusterer) { #ifndef GPUCA_GPUCODE - const CfFragment& fragment = clusterer.mPmemory->fragment; - CfArray2D chargeMap(reinterpret_cast(clusterer.mPchargeMap)); - - CfChargePos basePos(iBlock * PadsPerCacheline, 0); - - constexpr GPUTPCGeometry geo; - if (basePos.pad() >= geo.NPads(basePos.row())) { + if (iBlock >= (int32_t)GPUTPCGeometry::NROWS) { return; } - constexpr size_t ElemsInTileRow = (size_t)TilingLayout>::WidthInTiles * TimebinsPerCacheline * PadsPerCacheline; + constexpr GPUTPCGeometry geo; + const int32_t row = iBlock; + const int32_t nPads = geo.NPads(row); + const int32_t nVecPads = (nPads + PadsPerCacheline - 1) / PadsPerCacheline; + + const CfFragment& fragment = clusterer.mPmemory->fragment; + const bool hipFilterOn = clusterer.Param().rec.tpc.hipTailFilter; + const Charge hipTailThreshold = clusterer.Param().rec.tpc.hipTailFilterThreshold; + const Charge hipTailFilterAlpha = clusterer.Param().rec.tpc.hipTailFilterAlpha; + auto* nHIPTails = clusterer.mPnHIPTails; + auto* hipTails = GetHIPTails(clusterer, row); + + CfArray2D chargeMap(reinterpret_cast(clusterer.mPchargeMap)); using UShort8 = Vc::fixed_size_simd; + using Short8 = Vc::fixed_size_simd; using Charge8 = Vc::fixed_size_simd; - UShort8 totalCharges{Vc::Zero}; - UShort8 consecCharges{Vc::Zero}; - UShort8 maxConsecCharges{Vc::Zero}; - Charge8 maxCharge{Vc::Zero}; - - tpccf::TPCFragmentTime t = fragment.firstNonOverlapTimeBin(); - - // Access packed charges as raw integers. We throw away the PackedCharge type here to simplify vectorization. - const uint16_t* packedChargeStart = reinterpret_cast(&chargeMap[basePos.delta({0, t})]); - - for (; t < fragment.lastNonOverlapTimeBin(); t += TimebinsPerCacheline) { - for (tpccf::TPCFragmentTime localtime = 0; localtime < TimebinsPerCacheline; localtime++) { - const UShort8 packedCharges{packedChargeStart + PadsPerCacheline * localtime, Vc::Aligned}; - const UShort8::mask_type isCharge = packedCharges != 0; - - if (isCharge.isNotEmpty()) { - totalCharges(isCharge)++; - consecCharges += 1; - consecCharges(not isCharge) = 0; - maxConsecCharges = Vc::max(consecCharges, maxConsecCharges); - - // Manually unpack charges to float. - // Duplicated from PackedCharge::unpack to generate vectorized code: - // Charge unpack() const { return Charge(mVal & ChargeMask) / Charge(1 << DecimalBits); } - // Note that PackedCharge has to cut off the highest 2 bits via ChargeMask as they are used for flags by the cluster finder - // and are not part of the charge value. We can skip this step because the cluster finder hasn't run yet - // and thus these bits are guarenteed to be zero. - const Charge8 unpackedCharges = Charge8(packedCharges) / Charge(1 << PackedCharge::DecimalBits); - maxCharge = Vc::max(maxCharge, unpackedCharges); - } else { - consecCharges = 0; - } + std::vector totalChargesV(nVecPads, UShort8{Vc::Zero}); + std::vector consecChargesV(nVecPads, UShort8{Vc::Zero}); + std::vector maxConsecChargesV(nVecPads, UShort8{Vc::Zero}); + std::vector maxChargeV(nVecPads, Charge8{Vc::Zero}); + + std::vector localHipTbV(nVecPads, -1); + std::vector broadcastHipTbV(nVecPads, -1); + std::vector aboveThresholdStartV(nVecPads, -1); + std::vector activeHIPTailStartV(nVecPads, -1); + std::vector activeHIPTailEndV(nVecPads, -1); + std::vector tailFilterChargeV(nVecPads, Charge8{Vc::Zero}); + + for (int16_t t = 0; t < fragment.length; t += NumOfCachedTBs) { + + bool hasAnyTrigger = false; + + // Run actual noisy pad filter and look for HIP trigger + for (int16_t iVecPad = 0; iVecPad < nVecPads; iVecPad++) { + + auto totalCharges = totalChargesV[iVecPad]; + auto consecCharges = consecChargesV[iVecPad]; + auto maxConsecCharges = maxConsecChargesV[iVecPad]; + auto maxCharge = maxChargeV[iVecPad]; + + auto hipTb = Short8(-1); + auto aboveThresholdStart = aboveThresholdStartV[iVecPad]; + auto activeHIPTailStart = activeHIPTailStartV[iVecPad]; + auto activeHIPTailEnd = activeHIPTailEndV[iVecPad]; + auto tailFilterCharge = tailFilterChargeV[iVecPad]; + + const CfChargePos basePos(row, iVecPad * PadsPerCacheline, t); + + for (tpccf::TPCFragmentTime localtime = 0; localtime < NumOfCachedTBs; localtime++) { + + const uint16_t* packedChargeStart = reinterpret_cast(&chargeMap[basePos.delta({0, localtime})]); + const UShort8 packedCharges = t + localtime < fragment.length + ? UShort8{packedChargeStart, Vc::Aligned} + : UShort8{Vc::Zero}; + const auto isCharge = packedCharges != 0; + + const auto unpackedCharges = Charge8(packedCharges) / Charge(1 << PackedCharge::DecimalBits); + + if (isCharge.isNotEmpty()) { + totalCharges(isCharge)++; + consecCharges += 1; + consecCharges(not isCharge) = 0; + maxConsecCharges = Vc::max(consecCharges, maxConsecCharges); + + // Manually unpack charges to float. + // Duplicated from PackedCharge::unpack to generate vectorized code: + // Charge unpack() const { return Charge(mVal & ChargeMask) / Charge(1 << DecimalBits); } + // Note that PackedCharge has to cut off the highest 2 bits via ChargeMask as they are used for flags by the cluster finder + // and are not part of the charge value. We can skip this step because the cluster finder hasn't run yet + // and thus these bits are guarenteed to be zero. + maxCharge = Vc::max(maxCharge, unpackedCharges); + + const auto aboveRisingEdge = unpackedCharges >= hipTailThreshold; + const auto startRisingEdge = aboveRisingEdge && aboveThresholdStart < 0; + aboveThresholdStart(startRisingEdge) = t + localtime; + aboveThresholdStart(!aboveRisingEdge) = -1; + + const auto hasNewTrigger = hipTb < 0 && unpackedCharges >= Charge(MaxADC); + hipTb(hasNewTrigger) = aboveThresholdStart; + hasAnyTrigger |= hasNewTrigger.isNotEmpty(); + } else { + consecCharges = 0; + aboveThresholdStart = -1; + } + + const auto tailOpen = activeHIPTailStart > -1 && activeHIPTailEnd < 0; + tailFilterCharge(tailOpen) = tailFilterCharge + hipTailFilterAlpha * (unpackedCharges - tailFilterCharge); + activeHIPTailEnd(tailOpen && tailFilterCharge < hipTailThreshold) = t + localtime; + } // for (tpccf::TPCFragmentTime localtime = 0; localtime < TimebinsPerCacheline; localtime++) + + totalChargesV[iVecPad] = totalCharges; + consecChargesV[iVecPad] = consecCharges; + maxConsecChargesV[iVecPad] = maxConsecCharges; + maxChargeV[iVecPad] = maxCharge; + + localHipTbV[iVecPad] = hipTb; + aboveThresholdStartV[iVecPad] = aboveThresholdStart; + activeHIPTailStartV[iVecPad] = activeHIPTailStart; + activeHIPTailEndV[iVecPad] = activeHIPTailEnd; + tailFilterChargeV[iVecPad] = tailFilterCharge; + + } // for (int16_t iVecPad = 0; iVecPad < nVecPads; iVecPad++) + + if (hasAnyTrigger) { + broadcastHipTbV = localHipTbV; } - packedChargeStart += ElemsInTileRow; - } + // Broadcast trigger times to neighboring pads across the whole + for (int16_t iVecPad = 0; iVecPad < nVecPads && hasAnyTrigger; iVecPad++) { + + const auto hipTb = localHipTbV[iVecPad]; + + const auto hasHipTrigger = hipTb > -1; + if (hasHipTrigger.isNotEmpty()) [[unlikely]] { + + // TODO: This could be vectorised, but doesn't seem necessary + for (uint16_t p = 0; p < PadsPerCacheline; p++) { + if (hasHipTrigger[p]) { + const int16_t pad = iVecPad * PadsPerCacheline + p; + const int16_t neighborSt = CAMath::Max(0, pad - SSClusterPadWidth); + const int16_t neighborEnd = CAMath::Min(nPads, pad + SSClusterPadWidth + 1); + for (int16_t np = neighborSt; np < neighborEnd; np++) { + if (np == pad) { + continue; + } + const auto pv = np / PadsPerCacheline; + const auto pi = np % PadsPerCacheline; + // GPU keeps a pad's own trigger time; only pads without a local trigger inherit from neighbors. + if (localHipTbV[pv][pi] < 0) { + broadcastHipTbV[pv][pi] = CAMath::Max(hipTb[p], broadcastHipTbV[pv][pi]); + } + } // for (int16_t np = neighborSt; np < neighborEnd; np++) + } // if (hasHipTrigger[p]) { + } // for (uint16_t p = 0; p < PadsPerCacheline; p++) + } // if (hasHipTrigger.isNotEmpty()) + } // for (int16_t iVecPad = 0; iVecPad < nVecPads; iVecPad++) + + // Close old tails for all pads, open new tails in case of overlap + for (int16_t iVecPad = 0; iVecPad < nVecPads && hasAnyTrigger; iVecPad++) { + + auto hipTb = broadcastHipTbV[iVecPad]; + auto aboveThresholdStart = aboveThresholdStartV[iVecPad]; + auto activeHIPTailStart = activeHIPTailStartV[iVecPad]; + auto activeHIPTailEnd = activeHIPTailEndV[iVecPad]; + auto tailFilterCharge = tailFilterChargeV[iVecPad]; + + const auto shouldCloseTail = hipTb > -1 && activeHIPTailStart > -1; + activeHIPTailEnd(shouldCloseTail && activeHIPTailEnd < 0) = hipTb; + + // Closing tails will store them to global memory and zero the range + // So it's enough to disable this part to fully disable the tail filter + if (hipFilterOn && shouldCloseTail.isNotEmpty()) { + for (int16_t p = 0; p < PadsPerCacheline; p++) { + const int16_t pad = iVecPad * PadsPerCacheline + p; + if (shouldCloseTail[p] && pad < nPads) { + Charge tailQtot = 0; + Charge tailQMax = 0; + for (int16_t tt = activeHIPTailStart[p]; tt < activeHIPTailEnd[p]; tt++) { + const CfChargePos basePos(row, iVecPad * PadsPerCacheline, 0); + const auto pos = basePos.delta({p, tt}); + const auto q = chargeMap[pos].unpack(); + tailQtot += q; + tailQMax = CAMath::Max(tailQMax, q); + chargeMap[pos] = PackedCharge{0}; + } + + if (activeHIPTailEnd[p] > activeHIPTailStart[p]) { // Prune empty tails + const auto tailIdx = CAMath::AtomicAdd(&nHIPTails[row], 1) + 1; + if (tailIdx < GPUTPCCFHIPTailConnector::MaxHIPTailsPerRow) { + hipTails[tailIdx] = { + .iPrev = 0, + .iNext = 0, + .pad = uint16_t(pad), + .tailStart = uint16_t(activeHIPTailStart[p]), + .tailEnd = uint16_t(activeHIPTailEnd[p]), + .qTot = tailQtot, + .qMax = tailQMax, + }; + } + } + + } // if (shouldCloseTail[p] && pad < nPads) + } // for (uint16_t p = 0; p < PadsPerCacheline; p++) + } // if (shouldCloseThipFilterOn && shouldCloseTail.isNotEmpty()) + + activeHIPTailStart(hipTb > -1) = hipTb; + activeHIPTailEnd(hipTb > -1) = -1; + tailFilterCharge(hipTb > -1) = MaxADC; + + aboveThresholdStartV[iVecPad] = aboveThresholdStart; + activeHIPTailStartV[iVecPad] = activeHIPTailStart; + activeHIPTailEndV[iVecPad] = activeHIPTailEnd; + tailFilterChargeV[iVecPad] = tailFilterCharge; + + } // for (int32_t iVecPad = 0; iVecPad < nVecPads; iVecPad++) + } // for (auto t = 0; t < fragment.length; t += TimebinsPerCacheline) + + // Close old tails for all pads, open new tails in case of overlap + for (int16_t iVecPad = 0; iVecPad < nVecPads; iVecPad++) { + + auto activeHIPTailStart = activeHIPTailStartV[iVecPad]; + auto activeHIPTailEnd = activeHIPTailEndV[iVecPad]; + + const auto shouldCloseTail = activeHIPTailStart > -1; + activeHIPTailEnd(shouldCloseTail && activeHIPTailEnd < 0) = fragment.length; + + if (hipFilterOn && shouldCloseTail.isNotEmpty()) { + for (int16_t p = 0; p < PadsPerCacheline; p++) { + const int16_t pad = iVecPad * PadsPerCacheline + p; + if (shouldCloseTail[p] && pad < nPads) { + Charge tailQtot = 0; + Charge tailQMax = 0; + for (int16_t tt = activeHIPTailStart[p]; tt < activeHIPTailEnd[p]; tt++) { + const CfChargePos basePos(row, iVecPad * PadsPerCacheline, 0); + const auto pos = basePos.delta({p, tt}); + const auto q = chargeMap[pos].unpack(); + tailQtot += q; + tailQMax = CAMath::Max(tailQMax, q); + chargeMap[pos] = PackedCharge{0}; + } + + if (activeHIPTailEnd[p] > activeHIPTailStart[p]) { // Prune empty tails + const auto tailIdx = CAMath::AtomicAdd(&nHIPTails[row], 1) + 1; + if (tailIdx < GPUTPCCFHIPTailConnector::MaxHIPTailsPerRow) { + hipTails[tailIdx] = { + .iPrev = 0, + .iNext = 0, + .pad = uint16_t(pad), + .tailStart = uint16_t(activeHIPTailStart[p]), + .tailEnd = uint16_t(activeHIPTailEnd[p]), + .qTot = tailQtot, + .qMax = tailQMax, + }; + } + } + + } // if (shouldCloseTail[p] && pad < nPads) + } // for (uint16_t p = 0; p < PadsPerCacheline; p++) + } // if (hipFilterOn && shouldCloseTail.isNotEmpty()) + } // for (int16_t iVecPad = 0; iVecPad < nVecPads; iVecPad++) + + for (int32_t iVecPad = 0; iVecPad < nVecPads; iVecPad++) { + + const UShort8 totalCharges = totalChargesV[iVecPad]; + const UShort8 maxConsecCharges = maxConsecChargesV[iVecPad]; + const Charge8 maxCharge = maxChargeV[iVecPad]; + + const CfChargePos basePos(row, iVecPad * PadsPerCacheline, 0); - for (tpccf::Pad localpad = 0; localpad < PadsPerCacheline; localpad++) { - updatePadBaseline(basePos.gpad + localpad, clusterer, totalCharges[localpad], maxConsecCharges[localpad], maxCharge[localpad]); + for (tpccf::Pad localpad = 0; localpad < PadsPerCacheline; localpad++) { + updatePadBaseline(basePos.gpad + localpad, clusterer, totalCharges[localpad], maxConsecCharges[localpad], maxCharge[localpad]); + } } #endif } @@ -463,16 +668,23 @@ GPUd() void GPUTPCCFHIPTailConnector::Thread<0>(int32_t nBlocks, int32_t nThread #ifdef GPUCA_DETERMINISTIC_MODE // Races in tail comparisons and atomic swap can lead to slightly different clusters. // So need a sequential fallback for deterministic mode - if (iThread > 0) { - return; - } - nThreads = 1; GPUCommonAlgorithm::sortInBlock(tails + 1, tails + nTails + 1, [](auto&& t1, auto&& t2) { if (t1.pad != t2.pad) { return t1.pad < t2.pad; + } else if (t1.tailStart != t2.tailStart) { + return t1.tailStart < t2.tailStart; + } else if (t1.tailEnd != t2.tailEnd) { + return t1.tailEnd < t2.tailEnd; + } else if (t1.qTot != t2.qTot) { + return t1.qTot < t2.qTot; + } else { + return t1.qMax < t2.qMax; } - return t1.tailStart < t2.tailStart; }); + if (iThread > 0) { + return; + } + nThreads = 1; #endif for (uint32_t iTail = iThread + 1; iTail <= nTails; iTail += nThreads) { diff --git a/GPU/GPUTracking/TPCClusterFinder/GPUTPCCFCheckPadBaseline.h b/GPU/GPUTracking/TPCClusterFinder/GPUTPCCFCheckPadBaseline.h index c2c5a1e339256..08e6110ca2373 100644 --- a/GPU/GPUTracking/TPCClusterFinder/GPUTPCCFCheckPadBaseline.h +++ b/GPU/GPUTracking/TPCClusterFinder/GPUTPCCFCheckPadBaseline.h @@ -126,14 +126,6 @@ class GPUTPCCFCheckPadBaseline : public GPUKernelTemplate return gpudatatypes::RecoStep::TPCClusterFinding; } - static int32_t GetNBlocks(bool isGPU) - { - // Important to exclude rightmost padding from Pad Filter. - // There's nothing to filter there and padding is counted as start of a row, so it causes an overflow in the row count. - const int32_t nBlocksCPU = (TPC_CLUSTERER_STRIDED_PAD_COUNT - GPUCF_PADDING_PAD) / PadsPerCacheline; - return isGPU ? GPUTPCGeometry::NROWS : nBlocksCPU; - } - template GPUd() static void Thread(int32_t nBlocks, int32_t nThreads, int32_t iBlock, int32_t iThread, GPUSharedMemory& smem, processorType& clusterer); 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/GPUTracking/utils/VcShim.h b/GPU/GPUTracking/utils/VcShim.h index 2bbc1d471bbbb..b51210100c5a6 100644 --- a/GPU/GPUTracking/utils/VcShim.h +++ b/GPU/GPUTracking/utils/VcShim.h @@ -27,6 +27,7 @@ #include #include #include +#include namespace Vc { @@ -59,6 +60,7 @@ class WriteMaskVector V& mVec; public: + using vector_type = V; using value_type = typename V::value_type; WriteMaskVector(V& v, const M& m) : mMask(m), mVec(v) {} @@ -78,6 +80,15 @@ class WriteMaskVector } return *this; } + + WriteMaskVector& operator=(const vector_type& v) + { + for (size_t i = 0; i < mVec.size(); i++) { + if (mMask[i]) + mVec[i] = v[i]; + } + return *this; + } }; inline void prefetchMid(const void*) {} @@ -86,7 +97,7 @@ inline void prefetchForOneRead(const void*) {} } // namespace Common -template +template class fixed_size_simd_mask { private: @@ -95,7 +106,7 @@ class fixed_size_simd_mask public: bool isNotEmpty() const { return mData.any(); } - std::bitset::reference operator[](size_t i) { return mData[i]; } + typename std::bitset::reference operator[](size_t i) { return mData[i]; } bool operator[](size_t i) const { return mData[i]; } fixed_size_simd_mask operator!() const @@ -104,6 +115,13 @@ class fixed_size_simd_mask o.mData.flip(); return o; } + + fixed_size_simd_mask operator&&(const fixed_size_simd_mask& o) const + { + auto r = *this; + r.mData &= o.mData; + return r; + } }; template @@ -115,7 +133,7 @@ class fixed_size_simd public: using vector_type = std::array; using value_type = T; - using mask_type = fixed_size_simd_mask; + using mask_type = fixed_size_simd_mask; static constexpr size_t size() { return N; } @@ -130,6 +148,8 @@ class fixed_size_simd fixed_size_simd(const T* d, AlignedTag) { std::copy_n(d, N, mData.begin()); } + fixed_size_simd(const T& x) { mData.fill(x); } + T& operator[](size_t i) { return mData[i]; } const T& operator[](size_t i) const { return mData[i]; } @@ -149,6 +169,44 @@ class fixed_size_simd return *this; } + template + fixed_size_simd& operator+=(const fixed_size_simd& v) + { + for (size_t i = 0; i < N; i++) + mData[i] += v[i]; + return *this; + } + + fixed_size_simd& operator-=(const T& v) + { + for (auto& x : mData) + x -= v; + return *this; + } + + template + fixed_size_simd& operator-=(const fixed_size_simd& v) + { + for (size_t i = 0; i < N; i++) + mData[i] -= v[i]; + return *this; + } + + fixed_size_simd& operator*=(const T& v) + { + for (auto& x : mData) + x *= v; + return *this; + } + + template + fixed_size_simd& operator*=(const fixed_size_simd& v) + { + for (size_t i = 0; i < N; i++) + mData[i] *= v[i]; + return *this; + } + fixed_size_simd& operator/=(const T& v) { for (auto& x : mData) @@ -156,10 +214,12 @@ class fixed_size_simd return *this; } - fixed_size_simd operator/(const T& v) const + template + fixed_size_simd& operator/=(const fixed_size_simd& v) { - auto x = *this; - return x /= v; + for (size_t i = 0; i < N; i++) + mData[i] /= v[i]; + return *this; } mask_type operator==(const T& v) const @@ -172,10 +232,124 @@ class fixed_size_simd mask_type operator!=(const T& v) const { return !(*this == v); } + mask_type operator>(const T& v) const + { + mask_type m; + for (size_t i = 0; i < N; i++) + m[i] = mData[i] > v; + return m; + } + + mask_type operator>=(const T& v) const + { + mask_type m; + for (size_t i = 0; i < N; i++) + m[i] = mData[i] >= v; + return m; + } + + mask_type operator<(const T& v) const + { + mask_type m; + for (size_t i = 0; i < N; i++) + m[i] = mData[i] < v; + return m; + } + friend vector_type& internal_data<>(fixed_size_simd& x); friend const vector_type& internal_data<>(const fixed_size_simd& x); }; +template +struct is_fixed_size_simd : std::false_type { +}; + +template +struct is_fixed_size_simd> : std::true_type { +}; + +template +using EnableIfScalar = typename std::enable_if_t< + !is_fixed_size_simd>::value && std::is_convertible_v, int>; + +template +fixed_size_simd operator+(fixed_size_simd a, const fixed_size_simd& b) +{ + return a += b; +} + +template = 0> +fixed_size_simd operator+(fixed_size_simd a, const S& b) +{ + return a += static_cast(b); +} + +template = 0> +fixed_size_simd operator+(const S& a, fixed_size_simd b) +{ + return b += static_cast(a); +} + +template +fixed_size_simd operator-(fixed_size_simd a, const fixed_size_simd& b) +{ + return a -= b; +} + +template = 0> +fixed_size_simd operator-(fixed_size_simd a, const S& b) +{ + return a -= static_cast(b); +} + +template = 0> +fixed_size_simd operator-(const S& a, const fixed_size_simd& b) +{ + fixed_size_simd o; + for (size_t i = 0; i < N; i++) + o[i] = static_cast(a) - b[i]; + return o; +} + +template +fixed_size_simd operator*(fixed_size_simd a, const fixed_size_simd& b) +{ + return a *= b; +} + +template = 0> +fixed_size_simd operator*(fixed_size_simd a, const S& b) +{ + return a *= static_cast(b); +} + +template = 0> +fixed_size_simd operator*(const S& a, fixed_size_simd b) +{ + return b *= static_cast(a); +} + +template +fixed_size_simd operator/(fixed_size_simd a, const fixed_size_simd& b) +{ + return a /= b; +} + +template = 0> +fixed_size_simd operator/(fixed_size_simd a, const S& b) +{ + return a /= static_cast(b); +} + +template = 0> +fixed_size_simd operator/(const S& a, const fixed_size_simd& b) +{ + fixed_size_simd o; + for (size_t i = 0; i < N; i++) + o[i] = static_cast(a) / b[i]; + return o; +} + template V max(const V& a, const V& b) { 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/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..01a82ebdba361 100644 --- a/GPU/Workflow/src/GPUWorkflowSpec.cxx +++ b/GPU/Workflow/src/GPUWorkflowSpec.cxx @@ -260,8 +260,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) { 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 f8d001588b8f3..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]; @@ -286,15 +329,14 @@ std::vector GenTPCLoopers::importParticles() p_etot = TMath::Sqrt(px_p * px_p + py_p * py_p + pz_p * pz_p + mass_p * mass_p); // Push the electron TParticle electron(11, 1, -1, -1, -1, -1, px_e, py_e, pz_e, e_etot, vx, vy, vz, time / 1e9); - electron.SetStatusCode(o2::mcgenstatus::MCGenStatusEncoding(electron.GetStatusCode(), 0).fullEncoding); - electron.SetBit(ParticleStatus::kToBeDone, // - o2::mcgenstatus::getHepMCStatusCode(electron.GetStatusCode()) == 1); + // Setting HepMC status code != 1 to avoid detecting the loopers as physical primaries + electron.SetStatusCode(o2::mcgenstatus::MCGenStatusEncoding(2, 0).fullEncoding); + electron.SetBit(ParticleStatus::kToBeDone, true); particles.push_back(electron); // Push the positron TParticle positron(-11, 1, -1, -1, -1, -1, px_p, py_p, pz_p, p_etot, vx, vy, vz, time / 1e9); - positron.SetStatusCode(o2::mcgenstatus::MCGenStatusEncoding(positron.GetStatusCode(), 0).fullEncoding); - positron.SetBit(ParticleStatus::kToBeDone, // - o2::mcgenstatus::getHepMCStatusCode(positron.GetStatusCode()) == 1); + positron.SetStatusCode(o2::mcgenstatus::MCGenStatusEncoding(2, 0).fullEncoding); + positron.SetBit(ParticleStatus::kToBeDone, true); particles.push_back(positron); } // Get compton electrons from the event @@ -302,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]; @@ -312,9 +358,9 @@ std::vector GenTPCLoopers::importParticles() etot = TMath::Sqrt(px * px + py * py + pz * pz + mass_e * mass_e); // Push the electron TParticle electron(11, 1, -1, -1, -1, -1, px, py, pz, etot, vx, vy, vz, time / 1e9); - electron.SetStatusCode(o2::mcgenstatus::MCGenStatusEncoding(electron.GetStatusCode(), 0).fullEncoding); - electron.SetBit(ParticleStatus::kToBeDone, // - o2::mcgenstatus::getHepMCStatusCode(electron.GetStatusCode()) == 1); + // Setting HepMC status code != 1 to avoid detecting the loopers as physical primaries + electron.SetStatusCode(o2::mcgenstatus::MCGenStatusEncoding(2, 0).fullEncoding); + electron.SetBit(ParticleStatus::kToBeDone, true); particles.push_back(electron); } 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/prodtests/full-system-test/calib-workflow.sh b/prodtests/full-system-test/calib-workflow.sh old mode 100755 new mode 100644 index 3c05ca6cda303..72f7a5aa47056 --- a/prodtests/full-system-test/calib-workflow.sh +++ b/prodtests/full-system-test/calib-workflow.sh @@ -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,6 +73,10 @@ if [[ $CALIB_ASYNC_EXTRACTTIMESERIES == 1 ]] ; then fi : ${TPCTIMESERIES_SOURCES:=$TRACK_SOURCES} CONFIG_TPCTIMESERIES+=" --track-sources $TPCTIMESERIES_SOURCES" + 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 "${CONFIG_TPCTIMESERIES}" fi