Skip to content

TPC SC: faster and more robust space-charge distortion calculation - #16070

Open
matthias-kleiner wants to merge 1 commit into
AliceO2Group:devfrom
matthias-kleiner:tpc-spacecharge-perf-fixes
Open

matthias-kleiner wants to merge 1 commit into
AliceO2Group:devfrom
matthias-kleiner:tpc-spacecharge-perf-fixes

Conversation

@matthias-kleiner

Copy link
Copy Markdown
Contributor

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(), 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 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 with an incomplete type), independent of this change

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>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant