Skip to content

Commit ae9f329

Browse files
TPC SC: faster and more robust space-charge distortion calculation
Major improvements: - Poisson solver: anisotropy aware multigrid with phi line relaxation. Fine phi grids converge now (1800 phi bins did not converge before), relative stopping criterion independent of units and grid size, parallelised - tricubic interpolation without Vc, the three components of the fields and of the distortions/corrections are interpolated together - fix of the global distortions (standard method) at the readout and several other bug fixes in SpaceCharge, no crash or endless loop for invalid interpolation positions - faster filling of the density from histograms and faster reading of dumped files (ZSTD) - SpaceCharge objects read from a file are directly usable (the grid is set up by a read rule, initAfterReadingFromFile() is not needed anymore) - the slow space-charge correction is removed from TPCFastTransform - closure test of the global distortions and corrections Time per TPC side for calculateDistortionsCorrections() (default: fast global distortions), realistic space-charge density, 32 threads (AMD EPYC 7702P): grid (z x r x phi) old new speed-up 129 x 129 x 180 4.4 s 0.49 s 9x 257 x 257 x 180 50 s 1.6 s 32x 129 x 129 x 1800 68 s (*) 4.7 s 15x (*) Poisson solver not converged after 200 V-cycles Poisson solver only old new 129 x 129 x 180 3.4 s 0.11 s 257 x 257 x 180 47 s 0.33 s 129 x 129 x 1800 62 s (*) 1.55 s other old new density from TH3 (3600x257x257 -> 129x129x180) 11.7 s 0.3 s read all dumped containers of one side (1800 phi) 2.7 s 1.0 s The global distortions and corrections agree with the old code to a few um rms (max. ~30 um for r < 248 cm). Larger differences are only at the outer radial edge (extrapolation) and from the fixes listed below. Poisson solver - phi is only coarsened if its coupling (h_rz / (r_min * h_phi))^2 is not weak compared to r/z (MGParameters::phiCoarseningThreshold, default 0.5). On levels where the phi coupling dominates, each phi ring is solved exactly (periodic tridiagonal system, Thomas algorithm + Sherman-Morrison) instead of the point-wise relaxation, which does not smooth the error there (MGParameters::phiLineRelaxThreshold, default 4, 0 disables). The levels are described by a list of MGLevel. All levels converge in 3-15 V-cycles. - relative stopping criterion (MGParameters::relativeConvergence, default true): relative change of the potential between two V-cycles < 1e-8 (1e-6 for float, which stays at ~1e-7 because of the rounding). The previous absolute criterion (sum of dV^2 < 1e-6) depended on the units of the potential and on the grid size. The error w.r.t. the converged solution is now ~3e-9 of max|V|. relativeConvergence = false with setConvergenceError(1e-6) reproduces the previous criterion. - parallelised: red-black relaxation over phi, line relaxation (factorisation and blocks of lines), restriction, interpolation, convergence error, allocation of the level buffers and the copies between the V-cycles - removed unused options (available in the git history): 2D solver, 3D solver with constant number of phi slices, W cycle (not functional), Jacobi relaxation, half weighting, the symmetry argument (diverged; also removed from SpaceCharge::poissonSolver() and calcEField()) and the MGParameters isFull3D, cycleType, gtType, relaxType, maxLoop, gamma with their enums - merged duplicated code (interpolate3D<Add>(), restrictBoundary2D()) - fixes: minimum radius of the grid instead of the hard coded IFC radius for the coefficients, Vector3D constructor (list initialisation gave a single element) and size() Tricubic interpolation - separable Catmull-Rom interpolation (z, then r, then phi) with the values taken directly from the grid. The cell (position, index, weights) is calculated once for the three components (new TriCubicInterpolator::interpolate(z, r, phi, other1, other2, ...), used by NumericalFields::evalFields() and DistCorrInterpolator::evalDistCorr()) and the three sums are done in one pass. The results change at the rounding level (< 2.3e-14 of max|V| for double). - the cells at the boundary use a generic extrapolation of the stencil points outside of the grid instead of 26 hand-written cases. This fixes the extrapolation at the (first z, last r) and (last z, first r) edges, which was done along the wrong diagonal. - no Vc: Vector.h is removed (std::array in RegularGrid3D), the public headers and the dictionary do not include Vc anymore and TPCSpaceCharge does not link Vc - fixes: without extrapolation, queries at or beyond the last vertex in r or z returned the value one grid spacing inwards; the move constructor did not take over the extrapolation setting - non-finite query positions return NaN instead of reading outside of the grid, and positions far outside of the grid in phi are wrapped with fmod instead of loops which did not terminate for huge values SpaceCharge - calcGlobalDistortions(): in the last step before the readout the local distortions were scaled to the remaining drift length, but the correction for the missing distance assumed a full z bin, and std::abs turned the correction for electrons which passed the readout into an addition. The z bias of the standard method w.r.t. a direct integration of the fields changes from -83 um to -12 um, the closure with the global corrections in z from 110 um to 2.7 um rms. Standard and fast method agree in z to ~2 um. - correctElectron(): the z coordinate of the corrected point was set to y + corrY - calcGlobalDistCorrIterativeLinearCartesian(): the scaled contribution was lost for corrections with a scaling object; float precision of the angles - calcGlobalDistCorrIterative*(): reference to a member of scSCale also for scSCale == nullptr (undefined behaviour) - calcGlobalCorrections() with type 1: dR was copied to dRPhi and dZ for the vertices out of the volume - scaleChargeDensityStack(): phi of the vertex taken from the radial index - fillChargeDensityFromHisto(file, ...): nullptr dereference if a histogram is missing - getStepWidth(): integer division - DataContainer3D: the element-wise operators did not check the grid of the other container (out-of-bounds read) - reading from a file: the grid properties are transient and were only set up for the default number of vertices. Without calling initAfterReadingFromFile() an object with a different grid gave wrong values or read outside of the data. A read rule in the LinkDef now calls it for every SpaceCharge object read from a file (calling it explicitly, e.g. in the Digitizer, is still possible). - warning if the global distortions/corrections contain non-finite values (e.g. for unphysically large fields) - the functions processing both sides used only two threads (nested OpenMP regions are not active); the sides are processed one after the other with all threads, and the number of threads is passed to the Poisson solver - the density is rebinned from the histograms of both sides directly, without a merged copy (1.9 GB for 3600x257x257 bins) - iterative Simpson integration: the fields at the start point are evaluated once per step instead of once per iteration - calculateDistortionsCorrections() and initAllBuffers() allocate the containers concurrently: the first access to the memory of a large container by the single thread zeroing it took ~1.2 s per side at 1800 phi - the dumped containers use ZSTD instead of ZLIB compression: same file size (the data are hardly compressible), ~2.7 times faster reading. Old files are read as before. GPU TPCFastTransform - TPCSlowSpaceChargeCorrection held a SpaceCharge<float> object with the global corrections (setSlowTPCSCCorrection()), which replaced the spline correction in TransformLocal() on the CPU. It is not used anymore (the reconstruction uses the POD correction object) and is removed together with setSlowTPCSCCorrection(), getCorrectionSlow() and the dependency of TPCFastTransformation on O2::TPCSpaceCharge (O2::DataFormatsTPC is linked directly instead). - the persistent member mCorrectionSlow is removed: ClassDefNV of TPCFastTransform is increased to 6 (schema evolution). An empty dictionary-only stub of TPCSlowSpaceChargeCorrection is kept, so that reading existing objects (ClassDef <= 5, e.g. the CCDB maps) does not print a missing-dictionary warning. It can be removed once old maps are no longer read. Tests - Poisson solver: phi coarsening, phi line relaxation (720 phi bins) - tricubic interpolation: interpolation at all vertices, combined vs. separate interpolation of three components, phi spacing of the query points - new SpaceCharge test: closure of the global distortions (standard and fast method) with the global corrections from analytical fields on both sides, large fields and queries at invalid positions, reading of a stored object with a non-default grid (float and double) - createResidualDistortionObject.C is tested compiled only (COMPILE_ONLY, as the other space-charge macros and as documented in the macro): loading it in the interpreter crashed while auto-loading the CommonUtils dictionary (DebugStreamer's map of unique_ptr<TreeStreamRedirector> with an incomplete type), independent of this change Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent ab59052 commit ae9f329

23 files changed

Lines changed: 1546 additions & 2982 deletions

‎Detectors/TPC/spacecharge/CMakeLists.txt‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ o2_add_library(TPCSpaceCharge
1717
src/DataContainer3D.cxx
1818
PUBLIC_LINK_LIBRARIES O2::TPCBaseRecSim
1919
O2::Field
20-
Vc::Vc
2120
ROOT::Core
2221
O2::DataFormatsParameters)
2322

@@ -35,7 +34,6 @@ o2_target_root_dictionary(TPCSpaceCharge
3534
include/TPCSpaceCharge/PoissonSolverHelpers.h
3635
include/TPCSpaceCharge/SpaceChargeHelpers.h
3736
include/TPCSpaceCharge/TriCubic.h
38-
include/TPCSpaceCharge/Vector.h
3937
include/TPCSpaceCharge/Vector3D.h
4038
include/TPCSpaceCharge/SpaceChargeParameter.h
4139
LINKDEF src/TPCSpacechargeLinkDef.h)
@@ -49,7 +47,7 @@ o2_add_test_root_macro(macro/createResidualDistortionObject.C
4947
PUBLIC_LINK_LIBRARIES O2::TPCSpaceCharge
5048
O2::TPCSpaceChargeIO
5149
O2::CommonUtils
52-
LABELS tpc)
50+
LABELS tpc COMPILE_ONLY)
5351

5452

5553
install(FILES macro/createSCHistosFromHits.C
@@ -81,6 +79,14 @@ o2_add_test(TriCubic
8179
LABELS tpc
8280
CONFIGURATIONS RelWithDebInfo Release MinSizeRel)
8381

82+
o2_add_test(SpaceCharge
83+
COMPONENT_NAME spacecharge
84+
PUBLIC_LINK_LIBRARIES O2::TPCSpaceCharge
85+
SOURCES test/testO2TPCSpaceCharge.cxx
86+
ENVIRONMENT O2_ROOT=${CMAKE_BINARY_DIR}/stage
87+
LABELS tpc
88+
CONFIGURATIONS RelWithDebInfo Release MinSizeRel)
89+
8490
if (OpenMP_CXX_FOUND)
8591
target_compile_definitions(${targetName} PRIVATE WITH_OPENMP)
8692
target_link_libraries(${targetName} PRIVATE OpenMP::OpenMP_CXX)

‎Detectors/TPC/spacecharge/include/TPCSpaceCharge/DataContainer3D.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
#include "Rtypes.h"
2222
#include <vector>
23+
#include <string_view>
24+
#include <utility>
2325

2426
class TFile;
2527
class TTree;

‎Detectors/TPC/spacecharge/include/TPCSpaceCharge/PoissonSolver.h‎

Lines changed: 88 additions & 247 deletions
Large diffs are not rendered by default.

‎Detectors/TPC/spacecharge/include/TPCSpaceCharge/PoissonSolverHelpers.h‎

Lines changed: 8 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -26,37 +26,14 @@ namespace o2
2626
namespace tpc
2727
{
2828

29-
///< Enumeration of Cycles Type
30-
enum class CycleType {
31-
VCycle = 0, ///< V Cycle
32-
WCycle = 1, ///< W Cycle (TODO)
33-
FCycle = 2 ///< Full Cycle
34-
};
35-
36-
///< Fine -> Coarse Grid transfer operator types
37-
enum class GridTransferType {
38-
Half = 0, ///< Half weighting
39-
Full = 1, ///< Full weighting
40-
};
41-
42-
///< Smoothing (Relax) operator types
43-
enum class RelaxType {
44-
Jacobi = 0, ///< Jacobi (5 Stencil 2D, 7 Stencil 3D_
45-
WeightedJacobi = 1, ///< (TODO)
46-
GaussSeidel = 2 ///< Gauss Seidel 2D (2 Color, 5 Stencil), 3D (7 Stencil)
47-
};
48-
49-
struct MGParameters { ///< Parameters choice for MultiGrid algorithm
50-
inline static bool isFull3D = true; ///< TRUE: full coarsening, FALSE: semi coarsening
51-
inline static CycleType cycleType = CycleType::FCycle; ///< cycleType follow CycleType
52-
inline static GridTransferType gtType = GridTransferType::Full; ///< gtType grid transfer type follow GridTransferType
53-
inline static RelaxType relaxType = RelaxType::GaussSeidel; ///< relaxType follow RelaxType
54-
inline static int nPre = 2; ///< number of iteration for pre smoothing
55-
inline static int nPost = 2; ///< number of iteration for post smoothing
56-
inline static int nMGCycle = 200; ///< number of multi grid cycle (V type)
57-
inline static int maxLoop = 7; ///< the number of tree-deep of multi grid
58-
inline static int gamma = 1; ///< number of iteration at coarsest level !TODO SET TO REASONABLE VALUE!
59-
inline static int normalizeGridToNSector = SECTORSPERSIDE; ///< the grid in phi direction is squashed from 2 Pi to (2 Pi / SECTORSPERSIDE). This can used to get the potential for phi symmetric sc density or boundary potentials
29+
struct MGParameters { ///< Parameters of the multi grid algorithm
30+
inline static int nPre = 2; ///< number of iteration for pre smoothing
31+
inline static int nPost = 2; ///< number of iteration for post smoothing
32+
inline static int nMGCycle = 200; ///< maximum number of V cycles on each level of the full multi grid
33+
inline static int normalizeGridToNSector = SECTORSPERSIDE; ///< the grid in phi direction is squashed from 2 Pi to (2 Pi / SECTORSPERSIDE). This can used to get the potential for phi symmetric sc density or boundary potentials
34+
inline static float phiCoarseningThreshold = 0.5; ///< full 3D: the phi direction is only coarsened if the relative coupling in phi (h_{r,z} / (r_{min} * h_{phi}))^2 is larger than this value (weakly coupled directions should not be coarsened). 0: always coarsen phi if possible
35+
inline static float phiLineRelaxThreshold = 4; ///< full 3D: line relaxation in phi instead of point relaxation on the multi grid levels where the relative coupling in phi (h_{r,z} / (r_{min} * h_{phi}))^2 is larger than this value. 0: always point relaxation
36+
inline static bool relativeConvergence = true; ///< stopping criterion of the multi grid V cycles: true: relative change of the potential between two V cycles sqrt(sum dV^2 / sum V^2) (independent of the units of the potential and the grid size), false: maximum over the phi slices of the sum of dV^2 (depends on the units and on the grid size)
6037
};
6138

6239
template <typename DataT = double>

‎Detectors/TPC/spacecharge/include/TPCSpaceCharge/RegularGrid3D.h‎

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@
1717
#ifndef ALICEO2_TPC_REGULARGRID3D_H_
1818
#define ALICEO2_TPC_REGULARGRID3D_H_
1919

20-
#include "TPCSpaceCharge/Vector.h"
2120
#include "Rtypes.h" // for ClassDefNV
2221
#include "TPCSpaceCharge/SpaceChargeParameter.h"
22+
#include <array>
23+
#include <cmath>
2324
#include <vector>
2425

2526
namespace o2
@@ -90,17 +91,17 @@ struct RegularGrid3D {
9091
static constexpr unsigned int getFR() { return FR; } /// \return returns the index for dimension y (1)
9192
static constexpr unsigned int getFPhi() { return FPHI; } /// \return returns the index for dimension z (2)
9293

93-
const Vector<DataT, 3>& getGridMin() const { return mMin; } /// \return returns the minimum coordinates of the grid in all dimensions
94-
DataT getGridMinZ() const { return mMin[FZ]; } /// \return returns the minimum coordinate of the grid in x dimension
95-
DataT getGridMinR() const { return mMin[FR]; } /// \return returns the minimum coordinate of the grid in y dimension
96-
DataT getGridMinPhi() const { return mMin[FPHI]; } /// \return returns the minimum coordinate of the grid in z dimension
94+
const std::array<DataT, 3>& getGridMin() const { return mMin; } /// \return returns the minimum coordinates of the grid in all dimensions
95+
DataT getGridMinZ() const { return mMin[FZ]; } /// \return returns the minimum coordinate of the grid in x dimension
96+
DataT getGridMinR() const { return mMin[FR]; } /// \return returns the minimum coordinate of the grid in y dimension
97+
DataT getGridMinPhi() const { return mMin[FPHI]; } /// \return returns the minimum coordinate of the grid in z dimension
9798

9899
DataT getGridMaxZ() const { return mMax[FZ]; }
99100
DataT getGridMaxR() const { return mMax[FR]; }
100101
DataT getGridMaxPhi() const { return mMax[FPHI]; }
101102

102103
/// \return returns the inversed spacing of the grid for all dimensions
103-
const Vector<DataT, 3>& getInvSpacing() const { return mInvSpacing; }
104+
const std::array<DataT, 3>& getInvSpacing() const { return mInvSpacing; }
104105
DataT getInvSpacingZ() const { return mInvSpacing[FZ]; }
105106
DataT getInvSpacingR() const { return mInvSpacing[FR]; }
106107
DataT getInvSpacingPhi() const { return mInvSpacing[FPHI]; }
@@ -141,7 +142,7 @@ struct RegularGrid3D {
141142
/// \return returns the z positon for given vertex
142143
DataT getPhiVertex(const size_t vertexZ) const { return mPhiVertices[vertexZ]; }
143144

144-
const Vector<DataT, 3>& getMaxIndices() const { return sMaxIndex; } /// get max indices for all dimensions
145+
const std::array<DataT, 3>& getMaxIndices() const { return sMaxIndex; } /// get max indices for all dimensions
145146

146147
DataT getMaxIndexZ() const { return sMaxIndex[0]; } /// get max index in x direction
147148
DataT getMaxIndexR() const { return sMaxIndex[1]; } /// get max index in y direction
@@ -159,19 +160,19 @@ struct RegularGrid3D {
159160

160161
private:
161162
ParamSpaceCharge mParamGrid{};
162-
static constexpr unsigned int FDIM = 3; ///< dimensions of the grid (only 3 supported)
163-
static constexpr unsigned int FZ = 0; ///< index for x coordinate
164-
static constexpr unsigned int FR = 1; ///< index for y coordinate
165-
static constexpr unsigned int FPHI = 2; ///< index for z coordinate
166-
Vector<DataT, FDIM> mMin{}; ///<! min vertices positions of the grid
167-
Vector<DataT, FDIM> mMax{}; ///<! max vertices positions of the grid
168-
Vector<DataT, FDIM> mSpacing{}; ///<! spacing of the grid
169-
Vector<DataT, FDIM> mInvSpacing{}; ///<! inverse spacing of grid
170-
Vector<DataT, FDIM> sMaxIndex{{static_cast<DataT>(mParamGrid.NZVertices - 1.), static_cast<DataT>(mParamGrid.NRVertices - 1), static_cast<DataT>(mParamGrid.NPhiVertices - 1)}}; ///<! max index which is on the grid in all dimensions
171-
Vector<int, FDIM> sNdim{{static_cast<int>(mParamGrid.NZVertices), static_cast<int>(mParamGrid.NRVertices), static_cast<int>(mParamGrid.NPhiVertices)}}; ///<! number of vertices for each dimension
172-
std::vector<DataT> mZVertices{}; ///< positions of vertices in x direction
173-
std::vector<DataT> mRVertices{}; ///< positions of vertices in y direction
174-
std::vector<DataT> mPhiVertices{}; ///< positions of vertices in z direction
163+
static constexpr unsigned int FDIM = 3; ///< dimensions of the grid (only 3 supported)
164+
static constexpr unsigned int FZ = 0; ///< index for x coordinate
165+
static constexpr unsigned int FR = 1; ///< index for y coordinate
166+
static constexpr unsigned int FPHI = 2; ///< index for z coordinate
167+
std::array<DataT, FDIM> mMin{}; ///<! min vertices positions of the grid
168+
std::array<DataT, FDIM> mMax{}; ///<! max vertices positions of the grid
169+
std::array<DataT, FDIM> mSpacing{}; ///<! spacing of the grid
170+
std::array<DataT, FDIM> mInvSpacing{}; ///<! inverse spacing of grid
171+
std::array<DataT, FDIM> sMaxIndex{{static_cast<DataT>(mParamGrid.NZVertices - 1.), static_cast<DataT>(mParamGrid.NRVertices - 1), static_cast<DataT>(mParamGrid.NPhiVertices - 1)}}; ///<! max index which is on the grid in all dimensions
172+
std::array<int, FDIM> sNdim{{static_cast<int>(mParamGrid.NZVertices), static_cast<int>(mParamGrid.NRVertices), static_cast<int>(mParamGrid.NPhiVertices)}}; ///<! number of vertices for each dimension
173+
std::vector<DataT> mZVertices{}; ///< positions of vertices in x direction
174+
std::vector<DataT> mRVertices{}; ///< positions of vertices in y direction
175+
std::vector<DataT> mPhiVertices{}; ///< positions of vertices in z direction
175176

176177
void initLists();
177178

@@ -217,6 +218,11 @@ DataT RegularGrid3D<DataT>::clampToGridRel(const DataT pos, const unsigned int d
217218
template <typename DataT>
218219
DataT RegularGrid3D<DataT>::clampToGridCircular(DataT pos, const unsigned int dim) const
219220
{
221+
const DataT period = mMax[dim] - mMin[dim] + mSpacing[dim];
222+
if (!(std::abs(pos - mMin[dim]) < 4 * period)) {
223+
// far outside of the grid or not finite: wrap directly with the exact remainder instead of looping (a non-finite position becomes NaN)
224+
pos = mMin[dim] + std::fmod(pos - mMin[dim], period);
225+
}
220226
while (pos < mMin[dim]) {
221227
pos += mMax[dim] - mMin[dim] + mSpacing[dim];
222228
}
@@ -229,6 +235,11 @@ DataT RegularGrid3D<DataT>::clampToGridCircular(DataT pos, const unsigned int di
229235
template <typename DataT>
230236
DataT RegularGrid3D<DataT>::clampToGridCircularRel(DataT pos, const unsigned int dim) const
231237
{
238+
const DataT period = sNdim[dim];
239+
if (!(std::abs(pos) < 4 * period)) {
240+
// far outside of the grid or not finite: wrap directly with the exact remainder instead of looping (a non-finite position becomes NaN)
241+
pos = std::fmod(pos, period);
242+
}
232243
while (pos < 0) {
233244
pos += sNdim[dim];
234245
}

0 commit comments

Comments
 (0)