Project
Loading...
Searching...
No Matches
SpaceCharge.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
17
19#include "fmt/core.h"
20#include "Framework/Logger.h"
22#include "TGeoGlobalMagField.h"
25#include "Field/MagneticField.h"
27#include "TPCBase/CalDet.h"
29#include "MathUtils/Utils.h"
31#include "GPUDebugStreamer.h"
34
35#include <numeric>
36#include <chrono>
37#include "TF1.h"
38#include "TH3.h"
39#include "TH2F.h"
40#include "TGraph.h"
41#include "TCanvas.h"
42#include "TROOT.h"
43#include "TStopwatch.h"
44#include "ROOT/RDataFrame.hxx"
45#include "THnSparse.h"
46#include "TRandom.h"
47
48#include <random>
49
50#if defined(WITH_OPENMP) || defined(_OPENMP)
51#include <omp.h>
52#else
53static inline int omp_get_thread_num() { return 0; }
54static inline int omp_get_max_threads() { return 1; }
55#endif
56
58
59using namespace o2::tpc;
60
61template <typename DataT>
62SpaceCharge<DataT>::SpaceCharge(const int bfield, const unsigned short nZVertices, const unsigned short nRVertices, const unsigned short nPhiVertices, const bool initBuffers) : mParamGrid{nRVertices, nZVertices, nPhiVertices}
63{
64 ROOT::EnableThreadSafety();
65 initBField(bfield);
66 if (initBuffers) {
67 initAllBuffers();
68 }
69};
70
71template <typename DataT>
73{
74 ROOT::EnableThreadSafety();
75}
76
77template <typename DataT>
79{
80 for (int iside = 0; iside < SIDES; ++iside) {
81 const Side side = (iside == 0) ? Side::A : Side::C;
82 initContainer(mLocalDistdR[side], true);
83 initContainer(mLocalDistdZ[side], true);
84 initContainer(mLocalDistdRPhi[side], true);
85 initContainer(mLocalVecDistdR[side], true);
86 initContainer(mLocalVecDistdZ[side], true);
87 initContainer(mLocalVecDistdRPhi[side], true);
88 initContainer(mLocalCorrdR[side], true);
89 initContainer(mLocalCorrdZ[side], true);
90 initContainer(mLocalCorrdRPhi[side], true);
91 initContainer(mGlobalDistdR[side], true);
92 initContainer(mGlobalDistdZ[side], true);
93 initContainer(mGlobalDistdRPhi[side], true);
94 initContainer(mGlobalCorrdR[side], true);
95 initContainer(mGlobalCorrdZ[side], true);
96 initContainer(mGlobalCorrdRPhi[side], true);
97 initContainer(mDensity[side], true);
98 initContainer(mPotential[side], true);
99 initContainer(mElectricFieldEr[side], true);
100 initContainer(mElectricFieldEz[side], true);
101 initContainer(mElectricFieldEphi[side], true);
102 }
103}
104
105template <typename DataT>
107{
108 return std::clamp(omp_get_max_threads(), 1, 16);
109}
110
111template <typename DataT>
113{
114 using timer = std::chrono::high_resolution_clock;
116 if (!mDensity[side].getNDataPoints()) {
117 LOGP(info, "the charge is not set!");
118 }
119
120 const std::array<std::string, 2> sglobalType{"local distortion/correction interpolator", "Electric fields"};
121 const std::array<std::string, 2> sglobalDistType{"Standard method", "interpolation of global corrections"};
122 const std::array<std::string, 2> sideName{"A", "C"};
123
124 LOGP(info, "====== starting calculation of distortions and corrections for Side {} ======", sideName[side]);
125 LOGP(info, "Using {} threads", getNThreads());
126
127 if (getGlobalDistCorrMethod() == SC::GlobalDistCorrMethod::LocalDistCorr) {
128 LOGP(info, "calculation of global distortions and corrections are performed by using: {}", sglobalType[0]);
129 } else {
130 LOGP(info, "calculation of global distortions and corrections are performed by using: {}", sglobalType[1]);
131 }
132
133 if (getGlobalDistType() == SC::GlobalDistType::Fast) {
134 LOGP(info, "calculation of global distortions performed by following method: {}", sglobalDistType[1]);
135 } else if (getGlobalDistType() == SC::GlobalDistType::Standard) {
136 LOGP(info, "calculation of global distortions performed by following method: {}", sglobalDistType[0]);
137 } else {
138 LOGP(info, "skipping calculation of global distortions");
139 }
141 auto startTotal = timer::now();
142
143 poissonSolver(side);
144 calcEField(side);
145
146 const auto numEFields = getElectricFieldsInterpolator(side);
147 if (getGlobalDistType() == SC::GlobalDistType::Standard) {
148 auto start = timer::now();
150 calcLocalDistortionsCorrections(dist, numEFields); // local distortion calculation
151 auto stop = timer::now();
152 std::chrono::duration<float> time = stop - start;
153 LOGP(info, "local distortions time: {}", time.count());
154 } else {
155 LOGP(info, "skipping local distortions (not needed)");
156 }
158 auto start = timer::now();
160 calcLocalDistortionsCorrections(corr, numEFields); // local correction calculation
161 auto stop = timer::now();
162 std::chrono::duration<float> time = stop - start;
163 LOGP(info, "local corrections time: {}", time.count());
165 if (calcVectors) {
166 start = timer::now();
167 calcLocalDistortionCorrectionVector(numEFields);
168 stop = timer::now();
169 time = stop - start;
170 LOGP(info, "local correction/distortion vector time: {}", time.count());
171 }
172
173 start = timer::now();
174 const auto lCorrInterpolator = getLocalCorrInterpolator(side);
175 (getGlobalDistCorrMethod() == SC::GlobalDistCorrMethod::LocalDistCorr) ? calcGlobalCorrections(lCorrInterpolator) : calcGlobalCorrections(numEFields);
176 stop = timer::now();
177 time = stop - start;
178 LOGP(info, "global corrections time: {}", time.count());
179 start = timer::now();
180 if (getGlobalDistType() == SC::GlobalDistType::Fast) {
181 const auto globalCorrInterpolator = getGlobalCorrInterpolator(side);
182 calcGlobalDistWithGlobalCorrIterative(globalCorrInterpolator);
183 } else if (getGlobalDistType() == SC::GlobalDistType::Standard) {
184 const auto lDistInterpolator = getLocalDistInterpolator(side);
185 (getGlobalDistCorrMethod() == SC::GlobalDistCorrMethod::LocalDistCorr) ? calcGlobalDistortions(lDistInterpolator, 3 * sSteps * getNZVertices()) : calcGlobalDistortions(numEFields, 3 * sSteps * getNZVertices());
186 } else {
187 }
188
189 stop = timer::now();
190 time = stop - start;
191 LOGP(info, "global distortions time: {}", time.count());
192
193 stop = timer::now();
194 time = stop - startTotal;
195 LOGP(info, "everything is done. Total Time: {}", time.count());
196}
197
198template <typename DataT>
200{
201 const DataT minR = getRMinSim(side);
202 if (posR < minR) {
203 return minR;
204 }
205 const DataT maxR = getRMaxSim(side);
206 if (posR > maxR) {
207 return maxR;
208 }
209 return posR;
211
212template <typename DataT>
214{
215 const Side side = formulaStruct.getSide();
216 initContainer(mDensity[side], true);
217 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
218 const DataT phi = getPhiVertex(iPhi, side);
219 for (size_t iR = 0; iR < mParamGrid.NRVertices; ++iR) {
220 const DataT radius = getRVertex(iR, side);
221 for (size_t iZ = 0; iZ < mParamGrid.NZVertices; ++iZ) {
222 const DataT z = getZVertex(iZ, side);
223 mDensity[side](iZ, iR, iPhi) = formulaStruct.evalDensity(z, radius, phi);
224 }
225 }
226 }
227}
228
229template <typename DataT>
231{
232 std::function<DataT(DataT)> deltaPotFormula = [deltaPotential](const DataT) {
233 return deltaPotential;
234 };
235
236 setPotentialBoundaryGEMFrameAlongR(deltaPotFormula, side);
237 setPotentialBoundaryGEMFrameIROCBottomAlongPhi(deltaPotFormula, side);
238 setPotentialBoundaryGEMFrameIROCTopAlongPhi(deltaPotFormula, side);
239 setPotentialBoundaryGEMFrameOROC1BottomAlongPhi(deltaPotFormula, side);
240 setPotentialBoundaryGEMFrameOROC1TopAlongPhi(deltaPotFormula, side);
241 setPotentialBoundaryGEMFrameOROC2BottomAlongPhi(deltaPotFormula, side);
242 setPotentialBoundaryGEMFrameOROC2TopAlongPhi(deltaPotFormula, side);
243 setPotentialBoundaryGEMFrameOROC3BottomAlongPhi(deltaPotFormula, side);
244 setPotentialBoundaryGEMFrameOROC3TopAlongPhi(deltaPotFormula, side);
245}
246
247template <typename DataT>
249{
251 return 0;
252 }
253
254 const auto& regInf = Mapper::instance().getPadRegionInfo(0);
255 const float localYEdgeIROC = regInf.getPadsInRowRegion(0) / 2 * regInf.getPadWidth();
256 const auto globalPosGap = Mapper::LocalToGlobal(LocalPosition2D(regInf.getRadiusFirstRow(), -(localYEdgeIROC + GEMFrameParameters<DataT>::WIDTHFRAME)), Sector(0));
257 const auto phiGap = std::atan(globalPosGap.Y() / globalPosGap.X());
258
259 auto nBinsPhiGap = getNearestPhiVertex(phiGap, side);
260 if (nBinsPhiGap == 0) {
261 nBinsPhiGap = 1;
262 }
263
264 return nBinsPhiGap;
265}
266
267template <typename DataT>
268void SpaceCharge<DataT>::setPotentialBoundaryGEMFrameAlongR(const std::function<DataT(DataT)>& potentialFunc, const Side side)
269{
270 initContainer(mPotential[side], true);
271 const auto indices = getPotentialBoundaryGEMFrameAlongRIndices(side);
272 setBoundaryFromIndices(potentialFunc, indices, side);
273}
274
275template <typename DataT>
276std::vector<std::pair<size_t, float>> SpaceCharge<DataT>::getPotentialBoundaryGEMFrameAlongRIndices(const Side side) const
277{
278 const auto radiusStart = std::sqrt(std::pow(GEMFrameParameters<DataT>::LENGTHFRAMEIROCBOTTOM / 2, 2) + std::pow(GEMFrameParameters<DataT>::POSBOTTOM[0], 2));
279 const auto rStart = getNearestRVertex(radiusStart, side);
280
281 const auto radiusEnd = std::sqrt(std::pow(GEMFrameParameters<DataT>::LENGTHFRAMEOROC3TOP / 2, 2) + std::pow(GEMFrameParameters<DataT>::POSTOP[3], 2));
282 const auto rEnd = getNearestRVertex(radiusEnd, side); // mParamGrid.NRVertices - 1
283
284 const int verticesPerSector = mParamGrid.NPhiVertices / MGParameters::normalizeGridToNSector;
285
286 const auto& regInf = Mapper::instance().getPadRegionInfo(0);
287 const float localYEdgeIROC = regInf.getPadsInRowRegion(0) / 2 * regInf.getPadWidth();
288 const auto globalPosEdgeIROC = Mapper::LocalToGlobal(LocalPosition2D(regInf.getRadiusFirstRow(), -localYEdgeIROC), Sector(0));
289
290 const int stacks = sizeof(GEMFrameParameters<DataT>::POSTOP) / sizeof(GEMFrameParameters<DataT>::POSTOP[0]);
291
292 std::vector<DataT> radii;
293 for (int stack = 0; stack < stacks; ++stack) {
294 int region = 3;
295 if (stack == 1) {
296 region = 5;
297 } else if (stack == 2) {
298 region = 7;
299 } else if (stack == 3) {
300 region = 9;
301 }
302 const auto& regInf = Mapper::instance().getPadRegionInfo(region);
303 const float localYEdge = regInf.getPadsInRowRegion(region) / 2 * regInf.getPadWidth();
304 radii.emplace_back(std::sqrt(std::pow(GEMFrameParameters<DataT>::POSTOP[stack], 2) + std::pow(localYEdge, 2)));
305 }
306
307 std::vector<std::pair<size_t, float>> potentialInd;
308 const float weight = 1;
309 for (size_t iR = rStart; iR < rEnd; ++iR) {
310 const DataT radius = getRVertex(iR, side);
311 auto const it = std::lower_bound(radii.begin(), radii.end(), radius);
312 const int stack = (it == radii.end()) ? (stacks - 1) : (it - radii.begin());
313
314 // for stack 4 use the the number of phi bins at the edge
315 const auto radiusCompare = (stack == 4) ? GEMFrameParameters<DataT>::POSTOP[stack] : GEMFrameParameters<DataT>::POSTOP[stack] + (GEMFrameParameters<DataT>::POSTOP[stack] - GEMFrameParameters<DataT>::POSTOP[stack]) / 2;
316 for (size_t iPhiTmp = 0; iPhiTmp < getNPhiVertices(); ++iPhiTmp) {
317 const float margin = 0.5;
318 const DataT offsetGlobalY = radiusCompare * iPhiTmp * getGridSpacingPhi(side) + margin;
319 if (iPhiTmp > 0 && offsetGlobalY > globalPosEdgeIROC.Y()) {
320 break;
322
323 for (int sector = 0; sector < MGParameters::normalizeGridToNSector; ++sector) {
324 const size_t iPhiLeft = sector * verticesPerSector + iPhiTmp;
325 const size_t iZ = mParamGrid.NZVertices - 1;
326 potentialInd.emplace_back(mPotential[side].getDataIndex(iZ, iR, iPhiLeft), weight);
327 if (iPhiTmp > 0) {
328 const size_t iPhiRight = (sector + 1) * verticesPerSector - iPhiTmp;
329 potentialInd.emplace_back(mPotential[side].getDataIndex(iZ, iR, iPhiRight), weight);
331 }
332 }
333 }
334 std::sort(potentialInd.begin(), potentialInd.end());
335 return potentialInd;
336}
337
338template <typename DataT>
339void SpaceCharge<DataT>::setPotentialBoundaryGEMFrameAlongPhi(const std::function<DataT(DataT)>& potentialFunc, const GEMstack stack, const bool bottom, const Side side, const bool outerFrame)
340{
341 initContainer(mPotential[side], true);
342 const auto indices = getPotentialBoundaryGEMFrameAlongPhiIndices(stack, bottom, side, outerFrame);
343 setBoundaryFromIndices(potentialFunc, indices, side);
344}
345
346template <typename DataT>
347void SpaceCharge<DataT>::setBoundaryFromIndices(const std::function<DataT(DataT)>& potentialFunc, const std::vector<std::pair<size_t, float>>& indices, const Side side)
348{
349 /*
350 make check for the weights
351 Loop over bins in the radial direction
352 Check for duplicates and use the one with larger weight
353 */
354
355 for (const auto& indexw : indices) {
356 const int index = indexw.first;
357 const int iZ = mPotential[side].getIndexZ(index);
358 const int iR = mPotential[side].getIndexR(index);
359 const int iPhi = mPotential[side].getIndexPhi(index);
360 const DataT radius = getRVertex(iR, side);
361 const float weight = indexw.second;
362 const float pot = mPotential[side](iZ, iR, iPhi);
363 const float potNew = weight * potentialFunc(radius);
364 if (std::abs(potNew) > std::abs(pot)) {
365 mPotential[side](iZ, iR, iPhi) = potNew;
366 }
367 }
368}
369
370template <typename DataT>
371std::vector<std::pair<size_t, float>> SpaceCharge<DataT>::getPotentialBoundaryGEMFrameAlongPhiIndices(const GEMstack stack, const bool bottom, const Side side, const bool outerFrame, const bool noGap) const
372{
373 // to avoid double counting
374 auto indices = getPotentialBoundaryGEMFrameAlongRIndices(side);
376 int region = 0;
377 float offsStart = 0;
378 float offsEnd = 0;
379 if (bottom) {
380 offsEnd = -0.5;
382 region = 0;
383 } else if (stack == GEMstack::OROC1gem) {
384 region = 4;
385 } else if (stack == GEMstack::OROC2gem) {
386 region = 6;
387 } else if (stack == GEMstack::OROC3gem) {
388 region = 8;
389 }
390 } else {
391 offsStart = 0.5;
392 if (stack == GEMstack::IROCgem) {
393 region = 3;
394 } else if (stack == GEMstack::OROC1gem) {
395 region = 5;
396 } else if (stack == GEMstack::OROC2gem) {
397 region = 7;
398 } else if (stack == GEMstack::OROC3gem) {
399 region = 9;
402
403 const auto& regInf = Mapper::instance().getPadRegionInfo(region);
404 const auto radiusFirstRow = regInf.getRadiusFirstRow();
405 DataT radiusStart = bottom ? GEMFrameParameters<DataT>::POSBOTTOM[stack] : radiusFirstRow + regInf.getPadHeight() * regInf.getNumberOfPadRows();
406 if (bottom && (stack != GEMstack::IROCgem)) {
407 radiusStart -= 0.5;
408 }
409
410 auto radiusMax = bottom ? radiusFirstRow : GEMFrameParameters<DataT>::POSTOP[stack];
411
412 // add marging for first last padrows close to gap to improve numerical stabillity there
413 radiusStart += offsStart;
414 radiusMax += offsEnd;
415
416 int nVerticesR = (radiusMax - radiusStart) / getGridSpacingR(side);
417 if (nVerticesR == 0) {
418 nVerticesR = 1;
419 }
420
421 std::vector<std::pair<size_t, float>> potentialInd; // index, weight
422 const int verticesPerSector = mParamGrid.NPhiVertices / MGParameters::normalizeGridToNSector;
423 const auto nBinsPhi = (outerFrame || noGap) ? 0 : getPhiBinsGapFrame(side);
424 for (int sector = 0; sector < MGParameters::normalizeGridToNSector; ++sector) {
425 const auto offsetPhi = sector * verticesPerSector + verticesPerSector / 2;
426 for (size_t iPhiLocal = 0; iPhiLocal <= (verticesPerSector / 2 - nBinsPhi); ++iPhiLocal) {
427 const auto iPhiLeft = offsetPhi + iPhiLocal;
428 const auto iPhiRight = offsetPhi - iPhiLocal;
429 const DataT phiLeft = getPhiVertex(iPhiLeft, side);
430 const DataT phiRight = getPhiVertex(iPhiRight, side);
431 const DataT localphi = getPhiVertex(iPhiLocal, side);
432 const DataT radiusBottom = radiusStart / std::cos(localphi);
433 auto rStart = (outerFrame && (stack == GEMstack::IROCgem)) ? 1 : std::round((radiusBottom - getRMin(side)) / getGridSpacingR(side) + 0.5); // round up to use only bins whihc are fully covered
434 auto nREnd = (outerFrame && (stack == GEMstack::OROC3gem)) ? mParamGrid.NRVertices - 1 : rStart + nVerticesR;
435
436 // end at gem frame
437 if ((outerFrame && (stack == GEMstack::IROCgem))) {
438 // TODO: remove this?
439 const float marginCM = 0; // 0.4;
440 const int nMargingBins = marginCM / getGridSpacingR(side) + 0.5;
441 nREnd = (radiusBottom - getRVertex(1, side) + 0.5) / getGridSpacingR(side) + nMargingBins; // 2 safety margin
442 radiusMax = 3 + getRVertex(getNearestRVertex(radiusBottom + getGridSpacingR(side) * nMargingBins, side), side);
443 }
444
445 rStart -= 1;
446 nREnd += 1;
447 if (rStart <= 0) {
448 rStart = 1;
449 }
450
451 if (nREnd >= mParamGrid.NRVertices) {
452 nREnd = mParamGrid.NRVertices - 1;
453 }
455 float lxMin = radiusStart;
456 if ((outerFrame && (stack == GEMstack::IROCgem))) {
457 lxMin = 0;
458 }
459 const float lxMax = (nREnd == mParamGrid.NRVertices - 1) ? 9999 : radiusMax;
460 for (size_t iR = rStart; iR < nREnd; ++iR) {
461 const size_t iZ = mParamGrid.NZVertices - 1;
462 float weight = 1;
463 if (iPhiLeft < getNPhiVertices()) {
464 if (noGap || !std::binary_search(indices.begin(), indices.end(), std::make_pair(mPotential[side].getDataIndex(iZ, iR, iPhiLeft), 0.0f), [](const auto& a, const auto& b) { return (a.first < b.first); })) {
465
466 // check how much of the bin is in the lx range and assign weigth
467 const int nIterPoints = 1000;
468 int nPointsGood = 0;
469 for (int i = 0; i < nIterPoints; ++i) {
470 const float radius = getRVertex(iR, side) + getGridSpacingR(side) * gRandom->Uniform(-0.5, 0.5);
471 const float phi = getGridSpacingPhi(side) * gRandom->Uniform(-0.5, 0.5);
472 const DataT lx = radius * std::cos(phi + localphi);
473 if ((lx >= lxMin) && (lx <= lxMax)) {
474 ++nPointsGood;
475 }
476 }
477 weight = nPointsGood / double(nIterPoints);
478 potentialInd.emplace_back(mPotential[side].getDataIndex(iZ, iR, iPhiLeft), weight);
479 }
480 }
482 if (iPhiLocal && (noGap || !std::binary_search(indices.begin(), indices.end(), std::make_pair(mPotential[side].getDataIndex(iZ, iR, iPhiRight), 0.0f), [](const auto& a, const auto& b) { return (a.first < b.first); }))) {
483 potentialInd.emplace_back(mPotential[side].getDataIndex(iZ, iR, iPhiRight), weight);
484 }
485 }
486 }
487 }
488 // remove duplicate entries
489 std::sort(potentialInd.begin(), potentialInd.end());
491 // Remove duplicates
492 potentialInd.erase(std::unique(potentialInd.begin(), potentialInd.end()), potentialInd.end());
493
494 return potentialInd;
495}
496
497template <typename DataT>
498void SpaceCharge<DataT>::setPotentialBoundaryInnerRadius(const std::function<DataT(DataT)>& potentialFunc, const Side side)
500 initContainer(mPotential[side], true);
501 for (size_t iZ = 0; iZ < mParamGrid.NZVertices; ++iZ) {
502 const DataT z = getZVertex(iZ, side);
503 const auto pot = potentialFunc(z);
504 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
505 const size_t iR = 0;
506 mPotential[side](iZ, iR, iPhi) += pot;
507 }
508 }
509}
510
511template <typename DataT>
512void SpaceCharge<DataT>::setPotentialBoundaryOuterRadius(const std::function<DataT(DataT)>& potentialFunc, const Side side)
513{
514 initContainer(mPotential[side], true);
515 for (size_t iZ = 0; iZ < mParamGrid.NZVertices; ++iZ) {
516 const DataT z = getZVertex(iZ, side);
517 const auto pot = potentialFunc(z);
518 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
519 const size_t iR = mParamGrid.NRVertices - 1;
520 mPotential[side](iZ, iR, iPhi) = pot;
521 }
522 }
523}
524
525template <typename DataT>
527{
528 const Side side = formulaStruct.getSide();
529 initContainer(mPotential[side], true);
530 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
531 const DataT phi = getPhiVertex(iPhi, side);
532 for (size_t iR = 0; iR < mParamGrid.NRVertices; ++iR) {
533 const DataT radius = getRVertex(iR, side);
534 for (size_t iZ = 0; iZ < mParamGrid.NZVertices; ++iZ) {
535 const DataT z = getZVertex(iZ, side);
536 mPotential[side](iZ, iR, iPhi) = formulaStruct.evalPotential(z, radius, phi);
537 }
538 }
539 }
540}
541
542template <typename DataT>
543void SpaceCharge<DataT>::mirrorPotential(const Side sideRef, const Side sideMirrored)
545 initContainer(mPotential[sideRef], true);
546 initContainer(mPotential[sideMirrored], true);
547 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
548 for (size_t iR = 0; iR < mParamGrid.NRVertices; ++iR) {
549 for (size_t iZ = 0; iZ < mParamGrid.NZVertices; ++iZ) {
550 mPotential[sideMirrored](iZ, iR, iPhi) = mPotential[sideRef](iZ, iR, iPhi);
551 }
552 }
554}
555
556template <typename DataT>
558{
559 const Side side = formulaStruct.getSide();
560 initContainer(mPotential[side], true);
561 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
562 const DataT phi = getPhiVertex(iPhi, side);
563 for (size_t iZ = 0; iZ < mParamGrid.NZVertices; ++iZ) {
564 const DataT z = getZVertex(iZ, side);
565 const size_t iR = 0;
566 const DataT radius = getRVertex(iR, side);
567 mPotential[side](iZ, iR, iPhi) = formulaStruct.evalPotential(z, radius, phi);
568 }
569 }
570
571 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
572 const DataT phi = getPhiVertex(iPhi, side);
573 for (size_t iZ = 0; iZ < mParamGrid.NZVertices; ++iZ) {
574 const DataT z = getZVertex(iZ, side);
575 const size_t iR = mParamGrid.NRVertices - 1;
576 const DataT radius = getRVertex(iR, side);
577 mPotential[side](iZ, iR, iPhi) = formulaStruct.evalPotential(z, radius, phi);
578 }
579 }
581 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
582 const DataT phi = getPhiVertex(iPhi, side);
583 for (size_t iR = 0; iR < mParamGrid.NRVertices; ++iR) {
584 const DataT radius = getRVertex(iR, side);
585 const size_t iZ = 0;
586 const DataT z = getZVertex(iZ, side);
587 mPotential[side](iZ, iR, iPhi) = formulaStruct.evalPotential(z, radius, phi);
588 }
590
591 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
592 const DataT phi = getPhiVertex(iPhi, side);
593 for (size_t iR = 0; iR < mParamGrid.NRVertices; ++iR) {
594 const DataT radius = getRVertex(iR, side);
595 const size_t iZ = mParamGrid.NZVertices - 1;
596 const DataT z = getZVertex(iZ, side);
597 mPotential[side](iZ, iR, iPhi) = formulaStruct.evalPotential(z, radius, phi);
598 }
599 }
600}
601
602template <typename DataT>
603void SpaceCharge<DataT>::poissonSolver(const Side side, const DataT stoppingConvergence, const int symmetry)
604{
605 initContainer(mDensity[side], true);
606 initContainer(mPotential[side], true);
608 PoissonSolver<DataT> poissonSolver(mGrid3D[0]);
609 poissonSolver.poissonSolver3D(mPotential[side], mDensity[side], symmetry);
610}
611
612template <typename DataT>
613void SpaceCharge<DataT>::poissonSolver(const DataT stoppingConvergence, const int symmetry)
614{
615#pragma omp parallel for num_threads(2)
616 for (int iside = 0; iside < FNSIDES; ++iside) {
617 const o2::tpc::Side side = (iside == 0) ? Side::A : Side::C;
618 poissonSolver(side, stoppingConvergence, symmetry);
619 }
620}
621
622template <typename DataT>
624{
625 const Side side = formulaStruct.getSide();
626 initContainer(mElectricFieldEr[side], true);
627 initContainer(mElectricFieldEz[side], true);
628 initContainer(mElectricFieldEphi[side], true);
629 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
630 for (size_t iR = 0; iR < mParamGrid.NRVertices; ++iR) {
631 for (size_t iZ = 0; iZ < mParamGrid.NZVertices; ++iZ) {
632 const DataT radius = getRVertex(iR, side);
633 const DataT z = getZVertex(iZ, side);
634 const DataT phi = getPhiVertex(iPhi, side);
635 mElectricFieldEr[side](iZ, iR, iPhi) = formulaStruct.evalFieldR(z, radius, phi);
636 mElectricFieldEz[side](iZ, iR, iPhi) = formulaStruct.evalFieldZ(z, radius, phi);
637 mElectricFieldEphi[side](iZ, iR, iPhi) = formulaStruct.evalFieldPhi(z, radius, phi);
638 }
640 }
641}
642
643template <typename DataT>
645{
646 using timer = std::chrono::high_resolution_clock;
647 auto start = timer::now();
648 initContainer(mPotential[side], true);
649 initContainer(mElectricFieldEr[side], true);
650 initContainer(mElectricFieldEz[side], true);
651 initContainer(mElectricFieldEphi[side], true);
652#pragma omp parallel for num_threads(sNThreads)
653 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
654 const int symmetry = 0;
655 size_t tmpPlus = iPhi + 1;
656 int signPlus = 1;
657 int tmpMinus = static_cast<int>(iPhi - 1);
658 int signMinus = 1;
659 if (symmetry == 1 || symmetry == -1) { // Reflection symmetry in phi (e.g. symmetry at sector boundaries, or half sectors, etc.)
660 if (tmpPlus > mParamGrid.NPhiVertices - 1) {
661 if (symmetry == -1) {
662 signPlus = -1;
663 }
664 tmpPlus = mParamGrid.NPhiVertices - 2;
665 }
666 if (tmpMinus < 0) {
667 tmpMinus = 1; // SHOULD IT BE =0?
668 if (symmetry == -1) {
669 signMinus = -1;
670 }
671 }
672 } else { // No Symmetries in phi, no boundaries, the calculations is continuous across all phi
673 if (tmpPlus > mParamGrid.NPhiVertices - 1) {
674 tmpPlus = iPhi + 1 - mParamGrid.NPhiVertices;
675 }
676 if (tmpMinus < 0) {
677 tmpMinus = static_cast<int>(iPhi - 1 + mParamGrid.NPhiVertices);
678 }
679 }
680
681 // for non-boundary V
682 for (size_t iR = 1; iR < mParamGrid.NRVertices - 1; iR++) {
683 const DataT radius = getRVertex(iR, side);
684 for (size_t iZ = 1; iZ < mParamGrid.NZVertices - 1; iZ++) {
685 mElectricFieldEr[side](iZ, iR, iPhi) = -1 * (mPotential[side](iZ, iR + 1, iPhi) - mPotential[side](iZ, iR - 1, iPhi)) * static_cast<DataT>(0.5) * getInvSpacingR(side); // r direction
686 mElectricFieldEz[side](iZ, iR, iPhi) = -1 * (mPotential[side](iZ + 1, iR, iPhi) - mPotential[side](iZ - 1, iR, iPhi)) * static_cast<DataT>(0.5) * getInvSpacingZ(side); // z direction
687 mElectricFieldEphi[side](iZ, iR, iPhi) = -1 * (signPlus * mPotential[side](iZ, iR, tmpPlus) - signMinus * mPotential[side](iZ, iR, tmpMinus)) * static_cast<DataT>(0.5) * getInvSpacingPhi(side) / radius; // phi direction
689 }
690
691 // for boundary-r
692 for (size_t iZ = 0; iZ < mParamGrid.NZVertices; iZ++) {
693 mElectricFieldEr[side](iZ, 0, iPhi) = -1 * (-static_cast<DataT>(0.5) * mPotential[side](iZ, 2, iPhi) + 2 * mPotential[side](iZ, 1, iPhi) - static_cast<DataT>(1.5) * mPotential[side](iZ, 0, iPhi)) * getInvSpacingR(side); // forward difference
694 mElectricFieldEr[side](iZ, mParamGrid.NRVertices - 1, iPhi) = -1 * (static_cast<DataT>(1.5) * mPotential[side](iZ, mParamGrid.NRVertices - 1, iPhi) - 2 * mPotential[side](iZ, mParamGrid.NRVertices - 2, iPhi) + static_cast<DataT>(0.5) * mPotential[side](iZ, mParamGrid.NRVertices - 3, iPhi)) * getInvSpacingR(side); // backward difference
695 }
697 for (size_t iR = 0; iR < mParamGrid.NRVertices; iR += mParamGrid.NRVertices - 1) {
698 const DataT radius = getRVertex(iR, side);
699 for (size_t iZ = 1; iZ < mParamGrid.NZVertices - 1; iZ++) {
700 mElectricFieldEz[side](iZ, iR, iPhi) = -1 * (mPotential[side](iZ + 1, iR, iPhi) - mPotential[side](iZ - 1, iR, iPhi)) * static_cast<DataT>(0.5) * getInvSpacingZ(side); // z direction
701 mElectricFieldEphi[side](iZ, iR, iPhi) = -1 * (signPlus * mPotential[side](iZ, iR, tmpPlus) - signMinus * mPotential[side](iZ, iR, tmpMinus)) * static_cast<DataT>(0.5) * getInvSpacingPhi(side) / radius; // phi direction
702 }
703 }
705 // for boundary-z
706 for (size_t iR = 0; iR < mParamGrid.NRVertices; ++iR) {
707 mElectricFieldEz[side](0, iR, iPhi) = -1 * (-static_cast<DataT>(0.5) * mPotential[side](2, iR, iPhi) + 2 * mPotential[side](1, iR, iPhi) - static_cast<DataT>(1.5) * mPotential[side](0, iR, iPhi)) * getInvSpacingZ(side);
708 mElectricFieldEz[side](mParamGrid.NZVertices - 1, iR, iPhi) = -1 * (static_cast<DataT>(1.5) * mPotential[side](mParamGrid.NZVertices - 1, iR, iPhi) - 2 * mPotential[side](mParamGrid.NZVertices - 2, iR, iPhi) + static_cast<DataT>(0.5) * mPotential[side](mParamGrid.NZVertices - 3, iR, iPhi)) * getInvSpacingZ(side);
709 }
710
711 for (size_t iR = 1; iR < mParamGrid.NRVertices - 1; ++iR) {
712 const DataT radius = getRVertex(iR, side);
713 for (size_t iZ = 0; iZ < mParamGrid.NZVertices; iZ += mParamGrid.NZVertices - 1) {
714 mElectricFieldEr[side](iZ, iR, iPhi) = -1 * (mPotential[side](iZ, iR + 1, iPhi) - mPotential[side](iZ, iR - 1, iPhi)) * static_cast<DataT>(0.5) * getInvSpacingR(side); // r direction
715 mElectricFieldEphi[side](iZ, iR, iPhi) = -1 * (signPlus * mPotential[side](iZ, iR, tmpPlus) - signMinus * mPotential[side](iZ, iR, tmpMinus)) * static_cast<DataT>(0.5) * getInvSpacingPhi(side) / radius; // phi direction
716 }
717 }
718
719 // corner points for EPhi
720 for (size_t iR = 0; iR < mParamGrid.NRVertices; iR += mParamGrid.NRVertices - 1) {
721 const DataT radius = getRVertex(iR, side);
722 for (size_t iZ = 0; iZ < mParamGrid.NZVertices; iZ += mParamGrid.NZVertices - 1) {
723 mElectricFieldEphi[side](iZ, iR, iPhi) = -1 * (signPlus * mPotential[side](iZ, iR, tmpPlus) - signMinus * mPotential[side](iZ, iR, tmpMinus)) * static_cast<DataT>(0.5) * getInvSpacingPhi(side) / radius; // phi direction
724 }
725 }
726 }
727 auto stop = timer::now();
728 std::chrono::duration<float> time = stop - start;
729 const float totalTime = time.count();
730 LOGP(detail, "electric field calculation took {}s", totalTime);
731}
732
733template <typename DataT>
734void SpaceCharge<DataT>::calcGlobalDistWithGlobalCorrIterative(const DistCorrInterpolator<DataT>& globCorr, const int maxIter, const DataT approachZ, const DataT approachR, const DataT approachPhi, const DataT diffCorr, const SpaceCharge<DataT>* scSCale, float scale)
735{
736 calcGlobalDistCorrIterative(globCorr, maxIter, approachZ, approachR, approachPhi, diffCorr, scSCale, scale, Type::Distortions);
737}
738
739template <typename DataT>
740void SpaceCharge<DataT>::calcGlobalDistWithGlobalCorrIterative(const Side side, const SpaceCharge<DataT>* scSCale, float scale, const int maxIter, const DataT approachZ, const DataT approachR, const DataT approachPhi, const DataT diffCorr)
741{
742 calcGlobalDistCorrIterative(getGlobalCorrInterpolator(side), maxIter, approachZ, approachR, approachPhi, diffCorr, scSCale, scale, Type::Distortions);
743}
744
745template <typename DataT>
746void SpaceCharge<DataT>::calcGlobalDistWithGlobalCorrIterative(const SpaceCharge<DataT>* scSCale, float scale, const int maxIter, const DataT approachZ, const DataT approachR, const DataT approachPhi, const DataT diffCorr)
747{
748#pragma omp parallel for num_threads(sNThreads)
749 for (int iside = 0; iside < FNSIDES; ++iside) {
750 const o2::tpc::Side side = (iside == 0) ? Side::A : Side::C;
751 calcGlobalDistWithGlobalCorrIterative(side, scSCale, scale, maxIter, approachZ, approachR, approachPhi, diffCorr);
752 }
753}
754
755template <typename DataT>
756void SpaceCharge<DataT>::calcGlobalCorrWithGlobalDistIterative(const Side side, const SpaceCharge<DataT>* scSCale, float scale, const int maxIter, const DataT approachZ, const DataT approachR, const DataT approachPhi, const DataT diffCorr)
757{
758 calcGlobalDistCorrIterative(getGlobalDistInterpolator(side), maxIter, approachZ, approachR, approachPhi, diffCorr, scSCale, scale, Type::Corrections);
759}
760
761template <typename DataT>
762void SpaceCharge<DataT>::calcGlobalCorrWithGlobalDistIterative(const SpaceCharge<DataT>* scSCale, float scale, const int maxIter, const DataT approachZ, const DataT approachR, const DataT approachPhi, const DataT diffCorr)
763{
764#pragma omp parallel for num_threads(sNThreads)
765 for (int iside = 0; iside < FNSIDES; ++iside) {
766 const o2::tpc::Side side = (iside == 0) ? Side::A : Side::C;
767 calcGlobalCorrWithGlobalDistIterative(side, scSCale, scale, maxIter, approachZ, approachR, approachPhi, diffCorr);
768 }
769}
770
771template <typename DataT>
772void SpaceCharge<DataT>::calcGlobalDistCorrIterative(const DistCorrInterpolator<DataT>& globCorr, const int maxIter, const DataT approachZ, const DataT approachR, const DataT approachPhi, const DataT diffCorr, const SpaceCharge<DataT>* scSCale, float scale, const Type type)
773{
774 const Side side = globCorr.getSide();
775 if (type == Type::Distortions) {
776 initContainer(mGlobalDistdR[side], true);
777 initContainer(mGlobalDistdZ[side], true);
778 initContainer(mGlobalDistdRPhi[side], true);
779 } else {
780 initContainer(mGlobalCorrdR[side], true);
781 initContainer(mGlobalCorrdZ[side], true);
782 initContainer(mGlobalCorrdRPhi[side], true);
783 }
784
785 const auto& scSCaleInterpolator = (type == Type::Distortions) ? scSCale->mInterpolatorGlobalCorr[side] : scSCale->mInterpolatorGlobalDist[side];
786
787#pragma omp parallel for num_threads(sNThreads)
788 for (unsigned int iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
789 const DataT phi = getPhiVertex(iPhi, side);
790 for (unsigned int iR = 0; iR < mParamGrid.NRVertices; ++iR) {
791 const DataT radius = getRVertex(iR, side);
792 for (unsigned int iZ = 1; iZ < mParamGrid.NZVertices; ++iZ) {
793 const DataT z = getZVertex(iZ, side);
794 //
795 //==========================================================================================
796 //==== start algorithm: use tricubic upsampling to numerically approach the query point ====
797 //==========================================================================================
798 //
799 // 1. calculate difference from nearest point to query point with stepwidth factor x
800 // and approach the new point
801 //
802 DataT stepR = 0;
803 DataT stepZ = 0;
804 DataT stepPhi = 0;
805
806 // needed to check for convergence
807 DataT lastCorrdR = std::numeric_limits<DataT>::max();
808 DataT lastCorrdZ = std::numeric_limits<DataT>::max();
809 DataT lastCorrdRPhi = std::numeric_limits<DataT>::max();
810
811 // interpolated global correction
812 DataT corrdR = 0;
813 DataT corrdRPhi = 0;
814 DataT corrdZ = 0;
815
816 for (int iter = 0; iter < maxIter; ++iter) {
817 // 2. get new point coordinates
818 const DataT rCurrPos = radius + stepR;
819 const DataT zCurrPos = z + stepZ;
820 const DataT phiCurrPos = phi + stepPhi;
821
822 // abort calculation of drift path if electron reached inner/outer field cage or central electrode
823 if (rCurrPos <= getRMinSim(side) || rCurrPos >= getRMaxSim(side) || getSide(zCurrPos) != side) {
824 break;
825 }
826
827 // interpolate global correction at new point and calculate position of global correction
828 corrdR = globCorr.evaldR(zCurrPos, rCurrPos, phiCurrPos);
829 if (scSCale && scale != 0) {
830 corrdR += scale * scSCaleInterpolator.evaldR(zCurrPos, rCurrPos, phiCurrPos);
831 }
832 const DataT rNewPos = rCurrPos + corrdR;
833
834 DataT corrPhi = 0;
835 if (scSCale && scale != 0) {
836 corrPhi = scale * scSCaleInterpolator.evaldRPhi(zCurrPos, rCurrPos, phiCurrPos);
837 }
838 corrPhi += globCorr.evaldRPhi(zCurrPos, rCurrPos, phiCurrPos);
839 corrPhi /= rCurrPos;
840
841 corrdRPhi = corrPhi * rNewPos; // normalize to new r coordinate
842 const DataT phiNewPos = phiCurrPos + corrPhi;
843
844 corrdZ = globCorr.evaldZ(zCurrPos, rCurrPos, phiCurrPos);
845 if (scSCale && scale != 0) {
846 corrdZ += scale * scSCaleInterpolator.evaldZ(zCurrPos, rCurrPos, phiCurrPos);
847 }
848 const DataT zNewPos = zCurrPos + corrdZ;
849
850 // approach desired coordinate
851 stepR += (radius - rNewPos) * approachR;
852 stepZ += (z - zNewPos) * approachZ;
853 stepPhi += (phi - phiNewPos) * approachPhi;
854
855 // check for convergence
856 const DataT diffCorrdR = std::abs(corrdR - lastCorrdR);
857 const DataT diffCorrdRZ = std::abs(corrdZ - lastCorrdZ);
858 const DataT diffCorrdRPhi = std::abs(corrdRPhi - lastCorrdRPhi);
859
860 // stop algorithm if converged
861 if (diffCorrdR < diffCorr && diffCorrdRZ < diffCorr && diffCorrdRPhi < diffCorr) {
862 break;
863 }
864
865 lastCorrdR = corrdR;
866 lastCorrdZ = corrdZ;
867 lastCorrdRPhi = corrdRPhi;
868 }
869 // set global distortions if algorithm converged or iterations exceed max numbers of iterations
870 if (type == Type::Distortions) {
871 mGlobalDistdR[side](iZ, iR, iPhi) = -corrdR;
872 mGlobalDistdRPhi[side](iZ, iR, iPhi) = -corrdRPhi;
873 mGlobalDistdZ[side](iZ, iR, iPhi) = -corrdZ;
874 } else {
875 mGlobalCorrdR[side](iZ, iR, iPhi) = -corrdR;
876 mGlobalCorrdRPhi[side](iZ, iR, iPhi) = -corrdRPhi;
877 mGlobalCorrdZ[side](iZ, iR, iPhi) = -corrdZ;
878 }
879 }
880 }
881 for (unsigned int iR = 0; iR < mParamGrid.NRVertices; ++iR) {
882 if (type == Type::Distortions) {
883 mGlobalDistdR[side](0, iR, iPhi) = 3 * (mGlobalDistdR[side](1, iR, iPhi) - mGlobalDistdR[side](2, iR, iPhi)) + mGlobalDistdR[side](3, iR, iPhi);
884 mGlobalDistdRPhi[side](0, iR, iPhi) = 3 * (mGlobalDistdRPhi[side](1, iR, iPhi) - mGlobalDistdRPhi[side](2, iR, iPhi)) + mGlobalDistdRPhi[side](3, iR, iPhi);
885 mGlobalDistdZ[side](0, iR, iPhi) = 3 * (mGlobalDistdZ[side](1, iR, iPhi) - mGlobalDistdZ[side](2, iR, iPhi)) + mGlobalDistdZ[side](3, iR, iPhi);
886 } else {
887 mGlobalCorrdR[side](0, iR, iPhi) = 3 * (mGlobalCorrdR[side](1, iR, iPhi) - mGlobalCorrdR[side](2, iR, iPhi)) + mGlobalCorrdR[side](3, iR, iPhi);
888 mGlobalCorrdRPhi[side](0, iR, iPhi) = 3 * (mGlobalCorrdRPhi[side](1, iR, iPhi) - mGlobalCorrdRPhi[side](2, iR, iPhi)) + mGlobalCorrdRPhi[side](3, iR, iPhi);
889 mGlobalCorrdZ[side](0, iR, iPhi) = 3 * (mGlobalCorrdZ[side](1, iR, iPhi) - mGlobalCorrdZ[side](2, iR, iPhi)) + mGlobalCorrdZ[side](3, iR, iPhi);
890 }
891 }
892 }
893}
894
895template <typename DataT>
896void SpaceCharge<DataT>::calcGlobalDistWithGlobalCorrIterativeLinearCartesian(const Side side, const SpaceCharge<DataT>* scSCale, float scale, const int maxIter, const DataT approachX, const DataT approachY, const DataT approachZ, const DataT diffCorr)
897{
898 calcGlobalDistCorrIterativeLinearCartesian(getGlobalCorrInterpolator(side), maxIter, approachX, approachY, approachZ, diffCorr, scSCale, scale, Type::Distortions);
899}
901template <typename DataT>
902void SpaceCharge<DataT>::calcGlobalDistWithGlobalCorrIterativeLinearCartesian(const SpaceCharge<DataT>* scSCale, float scale, const int maxIter, const DataT approachX, const DataT approachY, const DataT approachZ, const DataT diffCorr)
903{
904#pragma omp parallel for num_threads(sNThreads)
905 for (int iside = 0; iside < FNSIDES; ++iside) {
906 const o2::tpc::Side side = (iside == 0) ? Side::A : Side::C;
907 calcGlobalDistWithGlobalCorrIterativeLinearCartesian(side, scSCale, scale, maxIter, approachX, approachY, approachZ, diffCorr);
908 }
909}
910
911template <typename DataT>
912void SpaceCharge<DataT>::calcGlobalCorrWithGlobalDistIterativeLinearCartesian(const Side side, const SpaceCharge<DataT>* scSCale, float scale, const int maxIter, const DataT approachX, const DataT approachY, const DataT approachZ, const DataT diffCorr)
913{
914 calcGlobalDistCorrIterativeLinearCartesian(getGlobalDistInterpolator(side), maxIter, approachX, approachY, approachZ, diffCorr, scSCale, scale, Type::Corrections);
915}
916
917template <typename DataT>
918void SpaceCharge<DataT>::calcGlobalCorrWithGlobalDistIterativeLinearCartesian(const SpaceCharge<DataT>* scSCale, float scale, const int maxIter, const DataT approachX, const DataT approachY, const DataT approachZ, const DataT diffCorr)
919{
920#pragma omp parallel for num_threads(sNThreads)
921 for (int iside = 0; iside < FNSIDES; ++iside) {
922 const o2::tpc::Side side = (iside == 0) ? Side::A : Side::C;
923 calcGlobalCorrWithGlobalDistIterativeLinearCartesian(side, scSCale, scale, maxIter, approachX, approachY, approachZ, diffCorr);
924 }
925}
926
927template <typename DataT>
928void SpaceCharge<DataT>::calcGlobalDistCorrIterativeLinearCartesian(const DistCorrInterpolator<DataT>& globCorr, const int maxIter, const DataT approachX, const DataT approachY, const DataT approachZ, const DataT diffCorr, const SpaceCharge<DataT>* scSCale, float scale, const Type type)
929{
930 const Side side = globCorr.getSide();
931 if (type == Type::Distortions) {
932 initContainer(mGlobalDistdR[side], true);
933 initContainer(mGlobalDistdZ[side], true);
934 initContainer(mGlobalDistdRPhi[side], true);
935 } else {
936 initContainer(mGlobalCorrdR[side], true);
937 initContainer(mGlobalCorrdZ[side], true);
938 initContainer(mGlobalCorrdRPhi[side], true);
939 }
940
941 const auto& scSCaleInterpolator = (type == Type::Distortions) ? scSCale->mInterpolatorGlobalCorr[side] : scSCale->mInterpolatorGlobalDist[side];
942
943#pragma omp parallel for num_threads(sNThreads)
944 for (unsigned int iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
945 const DataT phi = getPhiVertex(iPhi, side);
946 for (unsigned int iR = 0; iR < mParamGrid.NRVertices; ++iR) {
947 const DataT radius = getRVertex(iR, side);
948 const DataT x = getXFromPolar(radius, phi);
949 const DataT y = getYFromPolar(radius, phi);
950
951 for (unsigned int iZ = 1; iZ < mParamGrid.NZVertices; ++iZ) {
952 const DataT z = getZVertex(iZ, side);
953
954 DataT stepX = 0;
955 DataT stepY = 0;
956 DataT stepZ = 0;
957
958 // needed to check for convergence
959 DataT lastCorrX = std::numeric_limits<DataT>::max();
960 DataT lastCorrY = std::numeric_limits<DataT>::max();
961 DataT lastCorrZ = std::numeric_limits<DataT>::max();
962 DataT lastX = std::numeric_limits<DataT>::max();
963 DataT lastY = std::numeric_limits<DataT>::max();
964 DataT lastZ = std::numeric_limits<DataT>::max();
965
966 for (int iter = 0; iter < maxIter; ++iter) {
967 const DataT xCurrPos = x + stepX;
968 const DataT yCurrPos = y + stepY;
969 const DataT zCurrPos = z + stepZ;
970
971 // abort calculation of drift path if electron reached inner/outer field cage or central electrode
972 const DataT rCurrPos = getRadiusFromCartesian(xCurrPos, yCurrPos);
973 if (rCurrPos <= getRMinSim(side) || rCurrPos >= getRMaxSim(side) || getSide(zCurrPos) != side) {
974 break;
975 }
976
977 // interpolate global correction at new point and calculate position of global correction
978 DataT corrX = 0;
979 DataT corrY = 0;
980 DataT corrZ = 0;
981 (type == Type::Distortions) ? getCorrections(xCurrPos, yCurrPos, zCurrPos, side, corrX, corrY, corrZ) : getDistortions(xCurrPos, yCurrPos, zCurrPos, side, corrX, corrY, corrZ);
982
983 if (scSCale && scale != 0) {
984 DataT corrXScale = 0;
985 DataT corrYScale = 0;
986 DataT corrZScale = 0;
987 (type == Type::Distortions) ? scSCale->getCorrections(xCurrPos, yCurrPos, zCurrPos, side, corrXScale, corrYScale, corrZScale) : getDistortions(xCurrPos, yCurrPos, zCurrPos, side, corrX, corrY, corrZ);
988 corrX += scale * corrXScale;
989 corrY += scale * corrYScale;
990 corrZ += scale * corrZScale;
992
993 // check for convergence
994 const DataT diffCorrX = std::abs(corrX - lastCorrX);
995 const DataT diffCorrY = std::abs(corrY - lastCorrY);
996 const DataT diffCorrZ = std::abs(corrZ - lastCorrZ);
998 lastCorrX = corrX;
999 lastCorrY = corrY;
1000 lastCorrZ = corrZ;
1001 lastX = xCurrPos;
1002 lastY = yCurrPos;
1003 lastZ = zCurrPos;
1004
1005 // stop algorithm if converged
1006 if ((diffCorrX < diffCorr) && (diffCorrY < diffCorr) && (diffCorrZ < diffCorr)) {
1007 break;
1009
1010 const DataT xNewPos = xCurrPos + corrX;
1011 const DataT yNewPos = yCurrPos + corrY;
1012 const DataT zNewPos = zCurrPos + corrZ;
1014 // approach desired coordinate
1015 stepX += (x - xNewPos) * approachX;
1016 stepY += (y - yNewPos) * approachY;
1017 stepZ += (z - zNewPos) * approachZ;
1018 }
1019
1020 const DataT xNew = lastX + lastCorrX;
1021 const DataT yNew = lastY + lastCorrY;
1022 const DataT radiusNew = getRadiusFromCartesian(xNew, yNew);
1023 const DataT corrdR = -(radiusNew - getRadiusFromCartesian(lastX, lastY));
1024
1025 float phiNew = getPhiFromCartesian(xNew, yNew);
1028 float phiLast = getPhiFromCartesian(lastX, lastY);
1030
1031 DataT deltaPhi = (phiNew - phiLast);
1032 // handle edge cases
1033 if (deltaPhi > PI) {
1034 deltaPhi -= 2 * PI;
1035 } else if (deltaPhi < -PI) {
1036 deltaPhi += 2 * PI;
1037 }
1038 const DataT corrdRPhi = -deltaPhi * radiusNew;
1039
1040 // set global distortions if algorithm converged or iterations exceed max numbers of iterations
1041 if (type == Type::Distortions) {
1042 mGlobalDistdR[side](iZ, iR, iPhi) = corrdR;
1043 mGlobalDistdRPhi[side](iZ, iR, iPhi) = corrdRPhi;
1044 mGlobalDistdZ[side](iZ, iR, iPhi) = -lastCorrZ;
1045 } else {
1046 mGlobalCorrdR[side](iZ, iR, iPhi) = corrdR;
1047 mGlobalCorrdRPhi[side](iZ, iR, iPhi) = corrdRPhi;
1048 mGlobalCorrdZ[side](iZ, iR, iPhi) = -lastCorrZ;
1050 }
1051 }
1052 for (unsigned int iR = 0; iR < mParamGrid.NRVertices; ++iR) {
1053 if (type == Type::Distortions) {
1054 mGlobalDistdR[side](0, iR, iPhi) = 3 * (mGlobalDistdR[side](1, iR, iPhi) - mGlobalDistdR[side](2, iR, iPhi)) + mGlobalDistdR[side](3, iR, iPhi);
1055 mGlobalDistdRPhi[side](0, iR, iPhi) = 3 * (mGlobalDistdRPhi[side](1, iR, iPhi) - mGlobalDistdRPhi[side](2, iR, iPhi)) + mGlobalDistdRPhi[side](3, iR, iPhi);
1056 mGlobalDistdZ[side](0, iR, iPhi) = 3 * (mGlobalDistdZ[side](1, iR, iPhi) - mGlobalDistdZ[side](2, iR, iPhi)) + mGlobalDistdZ[side](3, iR, iPhi);
1057 } else {
1058 mGlobalCorrdR[side](0, iR, iPhi) = 3 * (mGlobalCorrdR[side](1, iR, iPhi) - mGlobalCorrdR[side](2, iR, iPhi)) + mGlobalCorrdR[side](3, iR, iPhi);
1059 mGlobalCorrdRPhi[side](0, iR, iPhi) = 3 * (mGlobalCorrdRPhi[side](1, iR, iPhi) - mGlobalCorrdRPhi[side](2, iR, iPhi)) + mGlobalCorrdRPhi[side](3, iR, iPhi);
1060 mGlobalCorrdZ[side](0, iR, iPhi) = 3 * (mGlobalCorrdZ[side](1, iR, iPhi) - mGlobalCorrdZ[side](2, iR, iPhi)) + mGlobalCorrdZ[side](3, iR, iPhi);
1061 }
1063 }
1064}
1065
1066template <typename DataT>
1069 if (!mElectricFieldEr[side].getNDataPoints()) {
1070 LOGP(warning, "============== E-Fields are not set! ==============");
1071 }
1072 NumericalFields<DataT> numFields(mElectricFieldEr[side], mElectricFieldEz[side], mElectricFieldEphi[side], mGrid3D[side], side);
1073 return numFields;
1075
1076template <typename DataT>
1078{
1079 if (!mLocalDistdR[side].getNDataPoints()) {
1080 LOGP(warning, "============== local distortions not set! ==============");
1081 }
1082 DistCorrInterpolator<DataT> numFields(mLocalDistdR[side], mLocalDistdZ[side], mLocalDistdRPhi[side], mGrid3D[side], side);
1083 return numFields;
1085
1086template <typename DataT>
1088{
1089 if (!mLocalCorrdR[side].getNDataPoints()) {
1090 LOGP(warning, "============== local corrections not set! ==============");
1091 }
1092 DistCorrInterpolator<DataT> numFields(mLocalCorrdR[side], mLocalCorrdZ[side], mLocalCorrdRPhi[side], mGrid3D[side], side);
1093 return numFields;
1094}
1095
1096template <typename DataT>
1098{
1099 if (!mGlobalDistdR[side].getNDataPoints()) {
1100 LOGP(warning, "============== global distortions not set ==============");
1102 DistCorrInterpolator<DataT> numFields(mGlobalDistdR[side], mGlobalDistdZ[side], mGlobalDistdRPhi[side], mGrid3D[side], side);
1103 return numFields;
1104}
1105
1106template <typename DataT>
1108{
1109 if (!mGlobalCorrdR[side].getNDataPoints()) {
1110 LOGP(warning, "============== global corrections not set ==============");
1111 }
1112 DistCorrInterpolator<DataT> numFields(mGlobalCorrdR[side], mGlobalCorrdZ[side], mGlobalCorrdRPhi[side], mGrid3D[side], side);
1113 return numFields;
1114}
1115
1116template <typename DataT>
1119 TH3* hisSCDensity3D = (TH3*)fInp.Get(name);
1120 fillChargeDensityFromHisto(*hisSCDensity3D);
1121 delete hisSCDensity3D;
1122}
1124template <typename DataT>
1126{
1127 const auto hConverted = o2::tpc::painter::convertCalDetToTH3(calSCDensity3D, true, mParamGrid.NRVertices, getRMin(Side::A), getRMax(Side::A), mParamGrid.NPhiVertices, getZMax(Side::A));
1128 fillChargeDensityFromHisto(hConverted);
1129}
1131template <typename DataT>
1132void SpaceCharge<DataT>::fillChargeFromCalDet(const std::vector<CalDet<float>>& calCharge3D)
1133{
1134 auto hConverted = o2::tpc::painter::convertCalDetToTH3(calCharge3D, false, mParamGrid.NRVertices, getRMin(Side::A), getRMax(Side::A), mParamGrid.NPhiVertices, getZMax(Side::A));
1135 normalizeHistoQVEps0(hConverted);
1136 fillChargeDensityFromHisto(hConverted);
1137}
1138
1139template <typename DataT>
1141{
1142 const unsigned short nBinsZNew = mParamGrid.NZVertices;
1143 const unsigned short nBinsRNew = mParamGrid.NRVertices;
1144 const unsigned short nBinsPhiNew = mParamGrid.NPhiVertices;
1145
1146 initContainer(mDensity[Side::A], true);
1147 initContainer(mDensity[Side::C], true);
1149 const int nBinsZNewTwo = 2 * nBinsZNew;
1150 const auto phiLow = hOrig.GetXaxis()->GetBinLowEdge(1);
1151 const auto phiUp = hOrig.GetXaxis()->GetBinUpEdge(hOrig.GetNbinsX());
1152 const auto rLow = hOrig.GetYaxis()->GetBinLowEdge(1);
1153 const auto rUp = hOrig.GetYaxis()->GetBinUpEdge(hOrig.GetNbinsY());
1154 const auto zLow = hOrig.GetZaxis()->GetBinLowEdge(1);
1155 const auto zUp = hOrig.GetZaxis()->GetBinUpEdge(hOrig.GetNbinsZ());
1156
1157 const int dim = 3;
1158 int bins[dim]{nBinsPhiNew, nBinsRNew, nBinsZNewTwo};
1159 double xmin[dim]{phiLow, rLow, zLow};
1160 double xmax[dim]{phiUp, rUp, zUp};
1161 const THnSparseF hRebin("hTmp", "hTmp", dim, bins, xmin, xmax);
1162
1163#pragma omp parallel for num_threads(sNThreads)
1164 for (int iBinPhi = 1; iBinPhi <= nBinsPhiNew; ++iBinPhi) {
1165 const auto phiLowEdge = hRebin.GetAxis(0)->GetBinLowEdge(iBinPhi);
1166 const auto phiUpEdge = hRebin.GetAxis(0)->GetBinUpEdge(iBinPhi);
1167
1168 const int phiLowBinOrig = hOrig.GetXaxis()->FindBin(phiLowEdge);
1169 const int phiUpBinOrig = hOrig.GetXaxis()->FindBin(phiUpEdge);
1170
1171 // calculate the weights (area of original bin lies in the new bin / binwidthOrig) of the first and last bins
1172 const auto binWidthPhiOrig = hOrig.GetXaxis()->GetBinWidth(phiLowBinOrig);
1173 const auto lowerBinWeightPhi = std::abs(phiLowEdge - hOrig.GetXaxis()->GetBinUpEdge(phiLowBinOrig)) / binWidthPhiOrig;
1174 const auto upperBinWeightPhi = std::abs(phiUpEdge - hOrig.GetXaxis()->GetBinLowEdge(phiUpBinOrig)) / binWidthPhiOrig;
1175
1176 for (int iBinR = 1; iBinR <= nBinsRNew; ++iBinR) {
1177 const auto rLowEdge = hRebin.GetAxis(1)->GetBinLowEdge(iBinR);
1178 const auto rUpEdge = hRebin.GetAxis(1)->GetBinUpEdge(iBinR);
1179
1180 const int rLowBinOrig = hOrig.GetYaxis()->FindBin(rLowEdge);
1181 const int rUpBinOrig = hOrig.GetYaxis()->FindBin(rUpEdge);
1182
1183 // calculate the weights (area of original bin lies in the new bin / binwidthOrig) of the first and last bins
1184 const auto binWidthROrig = hOrig.GetYaxis()->GetBinWidth(rLowBinOrig);
1185 const auto lowerBinWeightR = std::abs(rLowEdge - hOrig.GetYaxis()->GetBinUpEdge(rLowBinOrig)) / binWidthROrig;
1186 const auto upperBinWeightR = std::abs(rUpEdge - hOrig.GetYaxis()->GetBinLowEdge(rUpBinOrig)) / binWidthROrig;
1187
1188 for (int iBinZ = 1; iBinZ <= nBinsZNewTwo; ++iBinZ) {
1189 const auto zLowEdge = hRebin.GetAxis(2)->GetBinLowEdge(iBinZ);
1190 const auto zUpEdge = hRebin.GetAxis(2)->GetBinUpEdge(iBinZ);
1191 const auto zCenter = hRebin.GetAxis(2)->GetBinCenter(iBinZ);
1192
1193 int zLowBinOrig = hOrig.GetZaxis()->FindBin(zLowEdge);
1194 int zUpBinOrig = hOrig.GetZaxis()->FindBin(zUpEdge);
1195 const int currside = getSide(zCenter); // set the side of the current z-bin
1196 // get the side of the lowest and uppest bin from the orig histo
1197 const int sideLowOrig = getSide(hOrig.GetZaxis()->GetBinCenter(zLowBinOrig));
1198 const int sideUpOrig = getSide(hOrig.GetZaxis()->GetBinCenter(zUpBinOrig));
1199
1200 // make bounds/side check of the zLowBinOrig and zUpBinOrig bins. They must be on the same side as the currside!!!
1201 if (currside != sideLowOrig && zLowBinOrig != zUpBinOrig) {
1202 // if the lower bins from the orig histo are not on the same side as the rebinned increase the binnumber until they are on the same side
1203 bool notequal = true;
1204 do {
1205 zLowBinOrig += 1;
1206 if (zLowBinOrig > zUpBinOrig) {
1207 LOGP(warning, "SOMETHING WENT WRONG: SETTING BINS TO: {}", zUpBinOrig);
1208 zLowBinOrig = zUpBinOrig;
1209 notequal = false;
1210 }
1211 const int sideTmp = getSide(hOrig.GetZaxis()->GetBinCenter(zLowBinOrig));
1212 if (sideTmp == currside) {
1213 notequal = false;
1214 }
1215 } while (notequal);
1216 }
1217
1218 if (currside != sideUpOrig && zLowBinOrig != zUpBinOrig) {
1219 // if the upper bins from the orig histo are not on the same side as the rebinned increase the binnumber until they are on the same side
1220 bool notequal = true;
1221 do {
1222 zUpBinOrig -= 1;
1223 if (zUpBinOrig < zLowBinOrig) {
1224 LOGP(warning, "SOMETHING WENT WRONG: SETTING BINS TO: {}", zLowBinOrig);
1225 zUpBinOrig = zLowBinOrig;
1226 notequal = false;
1227 }
1228 const int sideTmp = getSide(hOrig.GetZaxis()->GetBinCenter(zUpBinOrig));
1229 if (sideTmp == currside) {
1230 notequal = false;
1231 }
1232 } while (notequal);
1233 }
1235 const auto binWidthZOrig = hOrig.GetZaxis()->GetBinWidth(zLowBinOrig);
1236 const auto lowerBinWeightZ = std::abs(zLowEdge - hOrig.GetZaxis()->GetBinUpEdge(zLowBinOrig)) / binWidthZOrig;
1237 const auto upperBinWeightZ = std::abs(zUpEdge - hOrig.GetZaxis()->GetBinLowEdge(zUpBinOrig)) / binWidthZOrig;
1239 // get the mean value of the original histogram of the found bin range
1240 DataT sum = 0;
1241 DataT sumW = 0;
1242 for (int iPhi = phiLowBinOrig; iPhi <= phiUpBinOrig; ++iPhi) {
1243 DataT weightPhi = 1;
1244 if (iPhi == phiLowBinOrig) {
1245 weightPhi = lowerBinWeightPhi;
1246 } else if (iPhi == phiUpBinOrig) {
1247 weightPhi = upperBinWeightPhi;
1248 }
1249
1250 for (int iR = rLowBinOrig; iR <= rUpBinOrig; ++iR) {
1251 DataT weightR = 1;
1252 if (iR == rLowBinOrig) {
1253 weightR = lowerBinWeightR;
1254 } else if (iR == rUpBinOrig) {
1255 weightR = upperBinWeightR;
1257
1258 for (int iZ = zLowBinOrig; iZ <= zUpBinOrig; ++iZ) {
1259 DataT weightZ = 1;
1260 if (iZ == zLowBinOrig) {
1261 weightZ = lowerBinWeightZ;
1262 } else if (iZ == zUpBinOrig) {
1263 weightZ = upperBinWeightZ;
1264 }
1265 const auto val = hOrig.GetBinContent(iPhi, iR, iZ);
1266 // if(val==0){
1267 // what to do now???
1268 // }
1269 const auto totalWeight = weightPhi * weightR * weightZ;
1270 sum += val * totalWeight;
1271 sumW += totalWeight;
1272 }
1273 }
1274 }
1275 sum /= sumW;
1276 const Side side = (iBinZ > mParamGrid.NZVertices) ? Side::A : Side::C;
1277 const int iZ = (side == Side::A) ? (iBinZ - mParamGrid.NZVertices - 1) : (mParamGrid.NZVertices - iBinZ);
1278 mDensity[side](iZ, iBinR - 1, iBinPhi - 1) = sum;
1279 }
1280 }
1281 }
1282}
1283
1284template <typename DataT>
1285template <typename ElectricFields>
1287{
1288 const Side side = formulaStruct.getSide();
1289 if (type == Type::Distortions) {
1290 initContainer(mLocalDistdR[side], true);
1291 initContainer(mLocalDistdZ[side], true);
1292 initContainer(mLocalDistdRPhi[side], true);
1293 } else {
1294 initContainer(mLocalCorrdR[side], true);
1295 initContainer(mLocalCorrdZ[side], true);
1296 initContainer(mLocalCorrdRPhi[side], true);
1297 }
1298
1299 // calculate local distortions/corrections for each vertex in the tpc
1300#pragma omp parallel for num_threads(sNThreads)
1301 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
1302 const DataT phi = getPhiVertex(iPhi, side);
1303 for (size_t iR = 0; iR < mParamGrid.NRVertices; ++iR) {
1304 const DataT radius = getRVertex(iR, side);
1305 for (size_t iZ = 0; iZ < mParamGrid.NZVertices - 1; ++iZ) {
1306 // set z coordinate depending on distortions or correction calculation
1307 const DataT z0 = type == Type::Corrections ? getZVertex(iZ + 1, side) : getZVertex(iZ, side);
1308 const DataT z1 = type == Type::Corrections ? getZVertex(iZ, side) : getZVertex(iZ + 1, side);
1309
1310 DataT drTmp = 0; // local distortion dR
1311 DataT dPhiTmp = 0; // local distortion dPhi (multiplication with R has to be done at the end)
1312 DataT dzTmp = 0; // local distortion dZ
1313
1314 const DataT stepSize = (z1 - z0) / sSteps; // the distortions are calculated by leting the elctron drift this distance in z direction
1315 for (int iter = 0; iter < sSteps; ++iter) {
1316 const DataT z0Tmp = (z0 + iter * stepSize + dzTmp); // starting z position
1317 const DataT z1Tmp = (z0Tmp + stepSize); // electron drifts from z0Tmp to z1Tmp
1318
1319 DataT ddR = 0; // distortion dR for drift from z0Tmp to z1Tmp
1320 DataT ddPhi = 0; // distortion dPhi for drift from z0Tmp to z1Tmp
1321 DataT ddZ = 0; // distortion dZ for drift from z0Tmp to z1Tmp
1322
1323 const DataT radiusTmp = regulateR(radius + drTmp, side); // current radial position
1324 const DataT phiTmp = regulatePhi(phi + dPhiTmp, side); // current phi position
1325
1326 // calculate distortions/corrections
1327 calcDistCorr(radiusTmp, phiTmp, z0Tmp, z1Tmp, ddR, ddPhi, ddZ, formulaStruct, true, side);
1328
1329 // add temp distortions to local distortions
1330 drTmp += ddR;
1331 dPhiTmp += ddPhi;
1332 dzTmp += ddZ;
1333 }
1334
1335 // store local distortions/corrections
1336 switch (type) {
1337 case Type::Corrections:
1338 mLocalCorrdR[side](iZ + 1, iR, iPhi) = drTmp;
1339 mLocalCorrdRPhi[side](iZ + 1, iR, iPhi) = dPhiTmp * radius;
1340 mLocalCorrdZ[side](iZ + 1, iR, iPhi) = dzTmp;
1341 break;
1342
1343 case Type::Distortions:
1344 mLocalDistdR[side](iZ, iR, iPhi) = drTmp;
1345 mLocalDistdRPhi[side](iZ, iR, iPhi) = dPhiTmp * radius;
1346 mLocalDistdZ[side](iZ, iR, iPhi) = dzTmp;
1347 break;
1348 }
1349 }
1350 // extrapolate local distortion/correction to last/first bin using legendre polynoms with x0=0, x1=1, x2=2 and x=-1. This has to be done to ensure correct interpolation in the last,second last/first,second bin!
1351 switch (type) {
1352 case Type::Corrections:
1353 mLocalCorrdR[side](0, iR, iPhi) = 3 * (mLocalCorrdR[side](1, iR, iPhi) - mLocalCorrdR[side](2, iR, iPhi)) + mLocalCorrdR[side](3, iR, iPhi);
1354 mLocalCorrdRPhi[side](0, iR, iPhi) = 3 * (mLocalCorrdRPhi[side](1, iR, iPhi) - mLocalCorrdRPhi[side](2, iR, iPhi)) + mLocalCorrdRPhi[side](3, iR, iPhi);
1355 mLocalCorrdZ[side](0, iR, iPhi) = 3 * (mLocalCorrdZ[side](1, iR, iPhi) - mLocalCorrdZ[side](2, iR, iPhi)) + mLocalCorrdZ[side](3, iR, iPhi);
1356 break;
1357
1358 case Type::Distortions:
1359 mLocalDistdR[side](mParamGrid.NZVertices - 1, iR, iPhi) = 3 * (mLocalDistdR[side](mParamGrid.NZVertices - 2, iR, iPhi) - mLocalDistdR[side](mParamGrid.NZVertices - 3, iR, iPhi)) + mLocalDistdR[side](mParamGrid.NZVertices - 4, iR, iPhi);
1360 mLocalDistdRPhi[side](mParamGrid.NZVertices - 1, iR, iPhi) = 3 * (mLocalDistdRPhi[side](mParamGrid.NZVertices - 2, iR, iPhi) - mLocalDistdRPhi[side](mParamGrid.NZVertices - 3, iR, iPhi)) + mLocalDistdRPhi[side](mParamGrid.NZVertices - 4, iR, iPhi);
1361 mLocalDistdZ[side](mParamGrid.NZVertices - 1, iR, iPhi) = 3 * (mLocalDistdZ[side](mParamGrid.NZVertices - 2, iR, iPhi) - mLocalDistdZ[side](mParamGrid.NZVertices - 3, iR, iPhi)) + mLocalDistdZ[side](mParamGrid.NZVertices - 4, iR, iPhi);
1362 break;
1363 }
1364 }
1365 }
1366}
1367
1368template <typename DataT>
1369template <typename ElectricFields>
1370void SpaceCharge<DataT>::calcLocalDistortionCorrectionVector(const ElectricFields& formulaStruct)
1371{
1372 const Side side = formulaStruct.getSide();
1373 initContainer(mLocalVecDistdR[side], true);
1374 initContainer(mLocalVecDistdZ[side], true);
1375 initContainer(mLocalVecDistdRPhi[side], true);
1376 initContainer(mElectricFieldEr[side], true);
1377 initContainer(mElectricFieldEz[side], true);
1378 initContainer(mElectricFieldEphi[side], true);
1379 // calculate local distortion/correction vector for each vertex in the tpc
1380#pragma omp parallel for num_threads(sNThreads)
1381 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
1382 for (size_t iR = 0; iR < mParamGrid.NRVertices; ++iR) {
1383 for (size_t iZ = 0; iZ < mParamGrid.NZVertices; ++iZ) {
1384 const DataT ezField = getEzField(formulaStruct.getSide());
1385 const DataT er = mElectricFieldEr[side](iZ, iR, iPhi);
1386 const DataT ez0 = mElectricFieldEz[side](iZ, iR, iPhi);
1387 const DataT ephi = mElectricFieldEphi[side](iZ, iR, iPhi);
1388 const DataT ez = getSign(formulaStruct.getSide()) * 1. / (ezField + ez0);
1389 const DataT erez = er * ez;
1390 const DataT ephiez = ephi * ez;
1391
1392 const DataT vecdR = mC0 * erez + mC1 * ephiez;
1393 const DataT vecdRPhi = mC0 * ephiez - mC1 * erez;
1394 const DataT vecdZ = -ez0 * TPCParameters<DataT>::DVDE;
1395
1396 mLocalVecDistdR[side](iZ, iR, iPhi) = vecdR;
1397 mLocalVecDistdRPhi[side](iZ, iR, iPhi) = vecdRPhi;
1398 mLocalVecDistdZ[side](iZ, iR, iPhi) = vecdZ;
1399 }
1400 }
1401 }
1402}
1403
1404template <typename DataT>
1405template <typename ElectricFields>
1407{
1408 if (type == Type::Distortions) {
1409 initContainer(mLocalDistdR[side], true);
1410 initContainer(mLocalDistdZ[side], true);
1411 initContainer(mLocalDistdRPhi[side], true);
1412 } else {
1413 initContainer(mLocalCorrdR[side], true);
1414 initContainer(mLocalCorrdZ[side], true);
1415 initContainer(mLocalCorrdRPhi[side], true);
1416 }
1417 // see: https://en.wikipedia.org/wiki/Runge%E2%80%93Kutta_methods
1418 // calculate local distortions/corrections for each vertex in the tpc using Runge Kutta 4 method
1419#pragma omp parallel for num_threads(sNThreads)
1420 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
1421 const DataT phi = getPhiVertex(iPhi, side);
1422 for (size_t iR = 0; iR < mParamGrid.NRVertices; ++iR) {
1423 const DataT radius = getRVertex(iR, side);
1424 for (size_t iZ = 0; iZ < mParamGrid.NZVertices - 1; ++iZ) {
1425 // set z coordinate depending on distortions or correction calculation
1426 const size_t iZ0 = type == Type::Corrections ? iZ + 1 : iZ;
1427 const size_t iZ1 = type == Type::Corrections ? iZ : iZ + 1;
1428
1429 const DataT z0 = getZVertex(iZ0, side);
1430 const DataT z1 = getZVertex(iZ1, side);
1431
1432 const DataT stepSize = z1 - z0; // h in the RK4 method
1433 const DataT absstepSize = std::abs(stepSize);
1434
1435 const DataT stepSizeHalf = 0.5 * stepSize; // half z bin for RK4
1436 const DataT absstepSizeHalf = std::abs(stepSizeHalf);
1437
1438 // starting position for RK4
1439 const DataT zk1 = z0;
1440 const DataT rk1 = radius;
1441 const DataT phik1 = phi;
1442
1443 DataT k1dR = 0; // derivative in r direction
1444 DataT k1dZ = 0; // derivative in z direction
1445 DataT k1dRPhi = 0; // derivative in rphi direction
1446
1447 // get derivative on current vertex
1448 switch (type) {
1449 case Type::Corrections:
1450 k1dR = getLocalVecCorrR(iZ0, iR, iPhi, side);
1451 k1dZ = getLocalVecCorrZ(iZ0, iR, iPhi, side);
1452 k1dRPhi = getLocalVecCorrRPhi(iZ0, iR, iPhi, side);
1453 break;
1454
1455 case Type::Distortions:
1456 k1dR = getLocalVecDistR(iZ0, iR, iPhi, side);
1457 k1dZ = getLocalVecDistZ(iZ0, iR, iPhi, side);
1458 k1dRPhi = getLocalVecDistRPhi(iZ0, iR, iPhi, side);
1459 break;
1460 }
1461
1462 // approximate position after half stepSize
1463 const DataT zk2 = zk1 + stepSizeHalf + absstepSizeHalf * k1dZ;
1464 const DataT rk2 = rk1 + absstepSizeHalf * k1dR;
1465 const DataT k1dPhi = k1dRPhi / rk1;
1466 const DataT phik2 = phik1 + absstepSizeHalf * k1dPhi;
1467
1468 // get derivative for new position
1469 DataT k2dR = 0;
1470 DataT k2dZ = 0;
1471 DataT k2dRPhi = 0;
1472 type == Type::Corrections ? getLocalCorrectionVectorCyl(zk2, rk2, phik2, side, k2dZ, k2dR, k2dRPhi) : getLocalDistortionVectorCyl(zk2, rk2, phik2, side, k2dZ, k2dR, k2dRPhi);
1473
1474 // approximate new position
1475 const DataT zk3 = zk1 + stepSizeHalf + absstepSizeHalf * k2dZ;
1476 const DataT rk3 = rk1 + absstepSizeHalf * k2dR;
1477 const DataT k2dPhi = k2dRPhi / rk2;
1478 const DataT phik3 = phik1 + absstepSizeHalf * k2dPhi;
1479
1480 DataT k3dR = 0;
1481 DataT k3dZ = 0;
1482 DataT k3dRPhi = 0;
1483 type == Type::Corrections ? getLocalCorrectionVectorCyl(zk3, rk3, phik3, side, k3dZ, k3dR, k3dRPhi) : getLocalDistortionVectorCyl(zk3, rk3, phik3, side, k3dZ, k3dR, k3dRPhi);
1484
1485 const DataT zk4 = zk1 + stepSize + absstepSize * k3dZ;
1486 const DataT rk4 = rk1 + absstepSize * k3dR;
1487 const DataT k3dPhi = k3dRPhi / rk3;
1488 const DataT phik4 = phik1 + absstepSize * k3dPhi;
1489
1490 DataT k4dR = 0;
1491 DataT k4dZ = 0;
1492 DataT k4dRPhi = 0;
1493 type == Type::Corrections ? getLocalCorrectionVectorCyl(zk4, rk4, phik4, side, k4dZ, k4dR, k4dRPhi) : getLocalDistortionVectorCyl(zk4, rk4, phik4, side, k4dZ, k4dR, k4dRPhi);
1494 const DataT k4dPhi = k4dRPhi / rk4;
1495
1496 // RK4 formula. See wikipedia: u = h * 1/6 * (k1 + 2*k2 + 2*k3 + k4)
1497 const DataT stepsizeSixth = absstepSize / 6;
1498 const DataT drRK = stepsizeSixth * (k1dR + 2 * k2dR + 2 * k3dR + k4dR);
1499 const DataT dzRK = stepsizeSixth * (k1dZ + 2 * k2dZ + 2 * k3dZ + k4dZ);
1500 const DataT dphiRK = stepsizeSixth * (k1dPhi + 2 * k2dPhi + 2 * k3dPhi + k4dPhi);
1501
1502 // store local distortions/corrections
1503 switch (type) {
1504 case Type::Corrections:
1505 mLocalCorrdR[side](iZ + 1, iR, iPhi) = drRK;
1506 mLocalCorrdRPhi[side](iZ + 1, iR, iPhi) = dphiRK * radius;
1507 mLocalCorrdZ[side](iZ + 1, iR, iPhi) = dzRK;
1508 break;
1509
1510 case Type::Distortions:
1511 mLocalDistdR[side](iZ, iR, iPhi) = drRK;
1512 mLocalDistdRPhi[side](iZ, iR, iPhi) = dphiRK * radius;
1513 mLocalDistdZ[side](iZ, iR, iPhi) = dzRK;
1514 break;
1515 }
1516 }
1517 // extrapolate local distortion/correction to last/first bin using legendre polynoms with x0=0, x1=1, x2=2 and x=-1. This has to be done to ensure correct interpolation in the last,second last/first,second bin!
1518 switch (type) {
1519 case Type::Corrections:
1520 mLocalCorrdR[side](0, iR, iPhi) = 3 * (mLocalCorrdR[side](1, iR, iPhi) - mLocalCorrdR[side](2, iR, iPhi)) + mLocalCorrdR[side](3, iR, iPhi);
1521 mLocalCorrdRPhi[side](0, iR, iPhi) = 3 * (mLocalCorrdRPhi[side](1, iR, iPhi) - mLocalCorrdRPhi[side](2, iR, iPhi)) + mLocalCorrdRPhi[side](3, iR, iPhi);
1522 mLocalCorrdZ[side](0, iR, iPhi) = 3 * (mLocalCorrdZ[side](1, iR, iPhi) - mLocalCorrdZ[side](2, iR, iPhi)) + mLocalCorrdZ[side](3, iR, iPhi);
1523 break;
1524
1525 case Type::Distortions:
1526 mLocalDistdR[side](mParamGrid.NZVertices - 1, iR, iPhi) = 3 * (mLocalDistdR[side](mParamGrid.NZVertices - 2, iR, iPhi) - mLocalDistdR[side](mParamGrid.NZVertices - 3, iR, iPhi)) + mLocalDistdR[side](mParamGrid.NZVertices - 4, iR, iPhi);
1527 mLocalDistdRPhi[side](mParamGrid.NZVertices - 1, iR, iPhi) = 3 * (mLocalDistdRPhi[side](mParamGrid.NZVertices - 2, iR, iPhi) - mLocalDistdRPhi[side](mParamGrid.NZVertices - 3, iR, iPhi)) + mLocalDistdRPhi[side](mParamGrid.NZVertices - 4, iR, iPhi);
1528 mLocalDistdZ[side](mParamGrid.NZVertices - 1, iR, iPhi) = 3 * (mLocalDistdZ[side](mParamGrid.NZVertices - 2, iR, iPhi) - mLocalDistdZ[side](mParamGrid.NZVertices - 3, iR, iPhi)) + mLocalDistdZ[side](mParamGrid.NZVertices - 4, iR, iPhi);
1529 break;
1530 }
1531 }
1532 }
1533}
1534
1535template <typename DataT>
1536template <typename Fields>
1537void SpaceCharge<DataT>::calcGlobalDistortions(const Fields& formulaStruct, const int maxIterations)
1538{
1539 const Side side = formulaStruct.getSide();
1540 initContainer(mGlobalDistdR[side], true);
1541 initContainer(mGlobalDistdZ[side], true);
1542 initContainer(mGlobalDistdRPhi[side], true);
1543 const DataT stepSize = formulaStruct.getID() == 2 ? getGridSpacingZ(side) : getGridSpacingZ(side) / sSteps; // if one used local distortions then no smaller stepsize is needed. if electric fields are used then smaller stepsize can be used
1544 // loop over tpc volume and let the electron drift from each vertex to the readout of the tpc
1545#pragma omp parallel for num_threads(sNThreads)
1546 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
1547 const DataT phi0 = getPhiVertex(iPhi, side);
1548 for (size_t iR = 0; iR < mParamGrid.NRVertices; ++iR) {
1549 const DataT r0 = getRVertex(iR, side);
1550 for (size_t iZ = 0; iZ < mParamGrid.NZVertices - 1; ++iZ) {
1551 const DataT z0 = getZVertex(iZ, side); // the electron starts at z0, r0, phi0
1552 DataT drDist = 0.0; // global distortion dR
1553 DataT dPhiDist = 0.0; // global distortion dPhi (multiplication with R has to be done at the end)
1554 DataT dzDist = 0.0; // global distortion dZ
1555 int iter = 0;
1556
1557 for (;;) {
1558 if (iter > maxIterations) {
1559 LOGP(error, "Aborting calculation of distortions for iZ: {}, iR: {}, iPhi: {} due to iteration '{}' > maxIterations '{}'!", iZ, iR, iPhi, iter, maxIterations);
1560 break;
1561 }
1562 const DataT z0Tmp = z0 + dzDist + iter * stepSize; // starting z position
1563
1564 // do not do check for first iteration
1565 if ((getSide(z0Tmp) != side) && iter) {
1566 LOGP(error, "Aborting calculation of distortions for iZ: {}, iR: {}, iPhi: {} due to change in the sides!", iZ, iR, iPhi);
1567 break;
1568 }
1569
1570 const DataT z1Tmp = z0Tmp + stepSize; // electron drifts from z0Tmp to z1Tmp
1571 const DataT radius = regulateR(r0 + drDist, side); // current radial position of the electron
1572 const DataT phi = regulatePhi(phi0 + dPhiDist, side); // current phi position of the electron
1573
1574 DataT ddR = 0; // distortion dR for drift from z0Tmp to z1Tmp
1575 DataT ddPhi = 0; // distortion dPhi for drift from z0Tmp to z1Tmp
1576 DataT ddZ = 0; // distortion dZ for drift from z0Tmp to z1Tmp
1577
1578 // get the distortion from interpolation of local distortions or calculate distortions with the electric field
1579 processGlobalDistCorr(radius, phi, z0Tmp, z1Tmp, ddR, ddPhi, ddZ, formulaStruct);
1580
1581 // if one uses local distortions the interpolated value for the last bin has to be scaled.
1582 // This has to be done because of the interpolated value is defined for a drift length of one z bin, but in the last bin the distance to the readout can be smaller than one z bin.
1583 const bool checkReached = side == Side::A ? z1Tmp >= getZMax(side) : z1Tmp <= getZMax(side);
1584 if (formulaStruct.getID() == 2 && checkReached) {
1585 const DataT fac = std::abs((getZMax(side) - z0Tmp) * getInvSpacingZ(side));
1586 ddR *= fac;
1587 ddZ *= fac;
1588 ddPhi *= fac;
1589 }
1590
1591 // add local distortions to global distortions
1592 drDist += ddR;
1593 dPhiDist += ddPhi;
1594 dzDist += ddZ;
1595
1596 // set loop to exit if the readout is reached and approximate distortion of 'missing' (one never ends exactly on the readout: z1Tmp + ddZ != ZMAX) drift distance.
1597 // approximation is done by the current calculated values of the distortions and scaled linear to the 'missing' distance.
1598 if (checkReached) {
1599 const DataT endPoint = z1Tmp + ddZ;
1600 const DataT deltaZ = getZMax(side) - endPoint; // distance from last point to read out
1601 const DataT diff = endPoint - z0Tmp;
1602 const DataT fac = diff != 0 ? std::abs(deltaZ / diff) : 0; // approximate the distortions for the 'missing' distance deltaZ
1603 drDist += ddR * fac;
1604 dPhiDist += ddPhi * fac;
1605 dzDist += ddZ * fac;
1606 break;
1607 }
1608 ++iter;
1609 }
1610 // store global distortions
1611 mGlobalDistdR[side](iZ, iR, iPhi) = drDist;
1612 mGlobalDistdRPhi[side](iZ, iR, iPhi) = dPhiDist * r0;
1613 mGlobalDistdZ[side](iZ, iR, iPhi) = dzDist;
1614 }
1615 }
1616 }
1617}
1618
1619template <typename DataT>
1620template <typename Formulas>
1621void SpaceCharge<DataT>::calcGlobalCorrections(const Formulas& formulaStruct, const int type)
1622{
1623 using timer = std::chrono::high_resolution_clock;
1624 auto start = timer::now();
1625 const Side side = formulaStruct.getSide();
1626 initContainer(mGlobalCorrdR[side], true);
1627 initContainer(mGlobalCorrdZ[side], true);
1628 initContainer(mGlobalCorrdRPhi[side], true);
1629
1630 const int iSteps = formulaStruct.getID() == 2 ? 1 : sSteps; // if one used local corrections no step width is needed. since it is already used for calculation of the local corrections
1631 const DataT stepSize = -getGridSpacingZ(side) / iSteps;
1632// loop over tpc volume and let the electron drift from each vertex to the readout of the tpc
1633#pragma omp parallel for num_threads(sNThreads)
1634 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
1635 const DataT phi0 = getPhiVertex(iPhi, side);
1636 for (size_t iR = 0; iR < mParamGrid.NRVertices; ++iR) {
1637
1638 const DataT r0 = getRVertex(iR, side);
1639 DataT drCorr = 0;
1640 DataT dPhiCorr = 0;
1641 DataT dzCorr = 0;
1642 bool isOutOfVolume = false;
1643
1644 // start at the readout and follow electron towards central electrode
1645 for (size_t iZ = mParamGrid.NZVertices - 1; iZ >= 1; --iZ) {
1646 const DataT z0 = getZVertex(iZ, side); // the electron starts at z0, r0, phi0
1647 // flag which is set when the central electrode is reached. if the central electrode is reached the calculation of the global corrections is aborted and the value set is the last calculated value.
1648 bool centralElectrodeReached = false;
1649 for (int iter = 0; iter < iSteps; ++iter) {
1650 if ((type != 3) && (centralElectrodeReached || isOutOfVolume)) {
1651 break;
1652 }
1653 DataT radius = r0 + drCorr; // current radial position of the electron
1654 DataT phi = phi0 + dPhiCorr; // current phi position of the electron
1655 const DataT z0Tmp = z0 + dzCorr + iter * stepSize; // starting z position
1656 DataT z1Tmp = z0Tmp + stepSize; // follow electron from z0Tmp to z1Tmp
1657
1658 // restrict to inner TPC volume
1659 if (type != 3) {
1660 radius = regulateR(radius, side);
1661 phi = regulatePhi(phi, side);
1662 z1Tmp = regulateZ(z1Tmp, side);
1663 }
1664
1665 DataT ddR = 0; // distortion dR for z0Tmp to z1Tmp
1666 DataT ddPhi = 0; // distortion dPhi for z0Tmp to z1Tmp
1667 DataT ddZ = 0; // distortion dZ for z0Tmp to z1Tmp
1668
1669 // get the distortion from interpolation of local distortions or calculate distortions with the electric field
1670 processGlobalDistCorr(radius, phi, z0Tmp, z1Tmp, ddR, ddPhi, ddZ, formulaStruct);
1671
1672 // if one uses local corrections the interpolated value for the first bin has to be scaled.
1673 // This has to be done because of the interpolated value is defined for a drift length of one z bin, but in the first bin the distance to the readout can be smaller than one z bin.
1674 centralElectrodeReached = getSign(side) * z1Tmp <= getZMin(side);
1675 if (formulaStruct.getID() == 2 && centralElectrodeReached) {
1676 const DataT fac = (z0Tmp - getZMin(side)) * getInvSpacingZ(side);
1677 ddR *= fac;
1678 ddZ *= fac;
1679 ddPhi *= fac;
1680 }
1681
1682 // calculate current r and z position after correction
1683 const DataT rCurr = r0 + drCorr + ddR;
1684 const DataT zCurr = z0Tmp + dzCorr + ddZ + stepSize;
1685
1686 // check if current position lies in the TPC volume if not the electron gets corrected outside of the TPC volume and the calculation of the following corrections can be skipped
1687 if ((type != 3) && (rCurr <= getRMinSim(side) || rCurr >= getRMaxSim(side) || (std::abs(zCurr) > 1.2 * std::abs(getZMax(side))))) {
1688 isOutOfVolume = true;
1689 break;
1690 }
1691
1692 // add local corrections to global corrections
1693 drCorr += ddR;
1694 dPhiCorr += ddPhi;
1695 dzCorr += ddZ;
1696
1697 // set loop to exit if the central electrode is reached and approximate correction of 'missing' (one never ends exactly on the central electrode: z1Tmp + ddZ != ZMIN) distance.
1698 // approximation is done by the current calculated values of the corrections and scaled linear to the 'missing' distance deltaZ. (NOT TESTED)
1699 if ((type != 3) && centralElectrodeReached) {
1700 const DataT endPoint = z1Tmp + ddZ;
1701 const DataT deltaZ = endPoint - getZMin(side);
1702 const DataT diff = z0Tmp - endPoint;
1703 const DataT fac = diff != 0 ? deltaZ / diff : 0; // approximate the distortions for the 'missing' distance deltaZ
1704 drCorr += ddR * fac;
1705 dPhiCorr += ddPhi * fac;
1706 dzCorr += ddZ * fac;
1707 break;
1708 }
1709 }
1710 // store global corrections
1711 if ((type == 1 || type == 2) && (centralElectrodeReached || isOutOfVolume)) {
1712 mGlobalCorrdR[side](iZ - 1, iR, iPhi) = -1;
1713 mGlobalCorrdRPhi[side](iZ - 1, iR, iPhi) = -1;
1714 mGlobalCorrdZ[side](iZ - 1, iR, iPhi) = -1;
1715 } else {
1716 mGlobalCorrdR[side](iZ - 1, iR, iPhi) = drCorr;
1717 mGlobalCorrdRPhi[side](iZ - 1, iR, iPhi) = dPhiCorr * r0;
1718 mGlobalCorrdZ[side](iZ - 1, iR, iPhi) = dzCorr;
1719 }
1720 }
1721 }
1722
1723 if (type != 0) {
1724 // fill / extrapolate out of volume values along r
1725 for (int iZ = mParamGrid.NZVertices - 1; iZ >= 0; --iZ) {
1726 // from middle of the radius to IFC
1727 for (int iR = (mParamGrid.NRVertices / 2); iR >= 0; --iR) {
1728 if ((mGlobalCorrdR[side](iZ, iR, iPhi) == -1) && (mGlobalCorrdRPhi[side](iZ, iR, iPhi) == -1) && (mGlobalCorrdZ[side](iZ, iR, iPhi) == -1)) {
1729 const size_t iRUp = iR + 1;
1730 if (type == 1) {
1731 // just replace with last valid number (assumption: values at iR==mParamGrid.NRVertices / 2 are valid)
1732 mGlobalCorrdR[side](iZ, iR, iPhi) = mGlobalCorrdR[side](iZ, iRUp, iPhi);
1733 mGlobalCorrdRPhi[side](iZ, iR, iPhi) = mGlobalCorrdR[side](iZ, iRUp, iPhi);
1734 mGlobalCorrdZ[side](iZ, iR, iPhi) = mGlobalCorrdR[side](iZ, iRUp, iPhi);
1735 } else if (type == 2) {
1736 // extrapolate values
1737 const size_t iRUpTwo = iR + 2;
1738 const size_t iRUpThree = iR + 3;
1739 mGlobalCorrdR[side](iZ, iR, iPhi) = 3 * (mGlobalCorrdR[side](iZ, iRUp, iPhi) - mGlobalCorrdR[side](iZ, iRUpTwo, iPhi)) + mGlobalCorrdR[side](iZ, iRUpThree, iPhi);
1740 mGlobalCorrdRPhi[side](iZ, iR, iPhi) = 3 * (mGlobalCorrdRPhi[side](iZ, iRUp, iPhi) - mGlobalCorrdRPhi[side](iZ, iRUpTwo, iPhi)) + mGlobalCorrdRPhi[side](iZ, iRUpThree, iPhi);
1741 mGlobalCorrdZ[side](iZ, iR, iPhi) = 3 * (mGlobalCorrdZ[side](iZ, iRUp, iPhi) - mGlobalCorrdZ[side](iZ, iRUpTwo, iPhi)) + mGlobalCorrdZ[side](iZ, iRUpThree, iPhi);
1742 }
1743 }
1744 }
1745 // from middle of the radius to OFC
1746 for (int iR = (mParamGrid.NRVertices / 2); iR < mParamGrid.NRVertices; ++iR) {
1747 if ((mGlobalCorrdR[side](iZ, iR, iPhi) == -1) && (mGlobalCorrdRPhi[side](iZ, iR, iPhi) == -1) && (mGlobalCorrdZ[side](iZ, iR, iPhi) == -1)) {
1748 const size_t iRUp = iR - 1;
1749 if (type == 1) {
1750 // just replace with last valid number (assumption: values at iR==mParamGrid.NRVertices / 2 are valid)
1751 mGlobalCorrdR[side](iZ, iR, iPhi) = mGlobalCorrdR[side](iZ, iRUp, iPhi);
1752 mGlobalCorrdRPhi[side](iZ, iR, iPhi) = mGlobalCorrdR[side](iZ, iRUp, iPhi);
1753 mGlobalCorrdZ[side](iZ, iR, iPhi) = mGlobalCorrdR[side](iZ, iRUp, iPhi);
1754 } else if (type == 2) {
1755 // extrapolate values
1756 const size_t iRUpTwo = iR - 2;
1757 const size_t iRUpThree = iR - 3;
1758 mGlobalCorrdR[side](iZ, iR, iPhi) = 3 * (mGlobalCorrdR[side](iZ, iRUp, iPhi) - mGlobalCorrdR[side](iZ, iRUpTwo, iPhi)) + mGlobalCorrdR[side](iZ, iRUpThree, iPhi);
1759 mGlobalCorrdRPhi[side](iZ, iR, iPhi) = 3 * (mGlobalCorrdRPhi[side](iZ, iRUp, iPhi) - mGlobalCorrdRPhi[side](iZ, iRUpTwo, iPhi)) + mGlobalCorrdRPhi[side](iZ, iRUpThree, iPhi);
1760 mGlobalCorrdZ[side](iZ, iR, iPhi) = 3 * (mGlobalCorrdZ[side](iZ, iRUp, iPhi) - mGlobalCorrdZ[side](iZ, iRUpTwo, iPhi)) + mGlobalCorrdZ[side](iZ, iRUpThree, iPhi);
1761 }
1762 }
1763 }
1764 }
1765 }
1766 }
1767 // set flag that global corrections are set to true
1768 auto stop = timer::now();
1769 std::chrono::duration<float> time = stop - start;
1770 const float totalTime = time.count();
1771 LOGP(detail, "calcGlobalCorrections took {}s", totalTime);
1772}
1773
1774template <typename DataT>
1776{
1777 DataT corrX{};
1778 DataT corrY{};
1779 DataT corrZ{};
1780 const Side side = getSide(point.Z());
1781
1782 // get the distortions for input coordinate
1783 getCorrections(point.X(), point.Y(), point.Z(), side, corrX, corrY, corrZ);
1784
1785 // set distorted coordinates
1786 point.SetXYZ(point.X() + corrX, point.Y() + corrY, point.Y() + corrY);
1787}
1788
1789template <typename DataT>
1790void SpaceCharge<DataT>::distortElectron(GlobalPosition3D& point, const SpaceCharge<DataT>* scSCale, float scale) const
1791{
1792 DataT distX{};
1793 DataT distY{};
1794 DataT distZ{};
1795 const Side side = getSide(point.Z());
1796 // get the distortions for input coordinate
1797 getDistortions(point.X(), point.Y(), point.Z(), side, distX, distY, distZ);
1798
1799 DataT distXTmp{};
1800 DataT distYTmp{};
1801 DataT distZTmp{};
1802
1803 // scale distortions if requested
1804 if (scSCale && scale != 0) {
1805 scSCale->getDistortions(point.X() + distX, point.Y() + distY, point.Z() + distZ, side, distXTmp, distYTmp, distZTmp);
1806 distX += distXTmp * scale;
1807 distY += distYTmp * scale;
1808 distZ += distZTmp * scale;
1809 }
1810
1811 GPUCA_DEBUG_STREAMER_CHECK(if (o2::utils::DebugStreamer::checkStream(o2::utils::StreamFlags::streamDistortionsSC)) {
1812 GlobalPosition3D pos(point);
1813 float phi = std::atan2(pos.Y(), pos.X());
1814 if (phi < 0.) {
1815 phi += TWOPI;
1816 }
1817 unsigned char secNum = std::floor(phi / SECPHIWIDTH);
1818 const Sector sector(secNum + (pos.Z() < 0) * SECTORSPERSIDE);
1820
1821 o2::utils::DebugStreamer::instance()->getStreamer("debug_distortElectron", "UPDATE") << o2::utils::DebugStreamer::instance()->getUniqueTreeName("debug_distortElectron").data()
1822 << "pos=" << pos
1823 << "lPos=" << lPos
1824 << "phi=" << phi
1825 << "secNum=" << secNum
1826 << "distX=" << distX
1827 << "distY=" << distY
1828 << "distZ=" << distZ
1829 << "distXDer=" << distXTmp
1830 << "distYDer=" << distYTmp
1831 << "distZDer=" << distZTmp
1832 << "scale=" << scale
1833 << "\n";
1834 })
1835
1836 // set distorted coordinates
1837 point.SetXYZ(point.X() + distX, point.Y() + distY, point.Z() + distZ);
1838}
1839
1840template <typename DataT>
1841DataT SpaceCharge<DataT>::getDensityCyl(const DataT z, const DataT r, const DataT phi, const Side side) const
1842{
1843 return mInterpolatorDensity[side](z, r, phi);
1844}
1845
1846template <typename DataT>
1847std::vector<float> SpaceCharge<DataT>::getDensityCyl(const std::vector<DataT>& z, const std::vector<DataT>& r, const std::vector<DataT>& phi, const Side side) const
1848{
1849 const auto nPoints = z.size();
1850 std::vector<float> density(nPoints);
1851#pragma omp parallel for num_threads(sNThreads)
1852 for (size_t i = 0; i < nPoints; ++i) {
1853 density[i] = getDensityCyl(z[i], r[i], phi[i], side);
1854 }
1855 return density;
1856}
1857
1858template <typename DataT>
1860{
1861 return mInterpolatorPotential[side](z, r, phi);
1862}
1863
1864template <typename DataT>
1865std::vector<float> SpaceCharge<DataT>::getPotentialCyl(const std::vector<DataT>& z, const std::vector<DataT>& r, const std::vector<DataT>& phi, const Side side) const
1866{
1867 const auto nPoints = z.size();
1868 std::vector<float> potential(nPoints);
1869#pragma omp parallel for num_threads(sNThreads)
1870 for (size_t i = 0; i < nPoints; ++i) {
1871 potential[i] = getPotentialCyl(z[i], r[i], phi[i], side);
1872 }
1873 return potential;
1874}
1875
1876template <typename DataT>
1877void SpaceCharge<DataT>::getElectricFieldsCyl(const DataT z, const DataT r, const DataT phi, const Side side, DataT& eZ, DataT& eR, DataT& ePhi) const
1878{
1879 eZ = mInterpolatorEField[side].evalFieldZ(z, r, phi);
1880 eR = mInterpolatorEField[side].evalFieldR(z, r, phi);
1881 ePhi = mInterpolatorEField[side].evalFieldPhi(z, r, phi);
1882}
1883
1884template <typename DataT>
1885void SpaceCharge<DataT>::getLocalCorrectionsCyl(const DataT z, const DataT r, const DataT phi, const Side side, DataT& lcorrZ, DataT& lcorrR, DataT& lcorrRPhi) const
1886{
1887 lcorrZ = mInterpolatorLocalCorr[side].evaldZ(z, r, phi);
1888 lcorrR = mInterpolatorLocalCorr[side].evaldR(z, r, phi);
1889 lcorrRPhi = mInterpolatorLocalCorr[side].evaldRPhi(z, r, phi);
1890}
1891
1892template <typename DataT>
1893void SpaceCharge<DataT>::getLocalCorrectionsCyl(const std::vector<DataT>& z, const std::vector<DataT>& r, const std::vector<DataT>& phi, const Side side, std::vector<DataT>& lcorrZ, std::vector<DataT>& lcorrR, std::vector<DataT>& lcorrRPhi) const
1894{
1895 const auto nPoints = z.size();
1896 lcorrZ.resize(nPoints);
1897 lcorrR.resize(nPoints);
1898 lcorrRPhi.resize(nPoints);
1899#pragma omp parallel for num_threads(sNThreads)
1900 for (size_t i = 0; i < nPoints; ++i) {
1901 getLocalCorrectionsCyl(z[i], r[i], phi[i], side, lcorrZ[i], lcorrR[i], lcorrRPhi[i]);
1902 }
1903}
1904
1905template <typename DataT>
1906void SpaceCharge<DataT>::getCorrectionsCyl(const DataT z, const DataT r, const DataT phi, const Side side, DataT& corrZ, DataT& corrR, DataT& corrRPhi) const
1907{
1908 corrZ = mInterpolatorGlobalCorr[side].evaldZ(z, r, phi);
1909 corrR = mInterpolatorGlobalCorr[side].evaldR(z, r, phi);
1910 corrRPhi = mInterpolatorGlobalCorr[side].evaldRPhi(z, r, phi);
1911}
1912
1913template <typename DataT>
1914void SpaceCharge<DataT>::getCorrectionsCyl(const std::vector<DataT>& z, const std::vector<DataT>& r, const std::vector<DataT>& phi, const Side side, std::vector<DataT>& corrZ, std::vector<DataT>& corrR, std::vector<DataT>& corrRPhi) const
1915{
1916 const auto nPoints = z.size();
1917 corrZ.resize(nPoints);
1918 corrR.resize(nPoints);
1919 corrRPhi.resize(nPoints);
1920#pragma omp parallel for num_threads(sNThreads)
1921 for (size_t i = 0; i < nPoints; ++i) {
1922 getCorrectionsCyl(z[i], r[i], phi[i], side, corrZ[i], corrR[i], corrRPhi[i]);
1923 }
1924}
1925
1926template <typename DataT>
1927void SpaceCharge<DataT>::getCorrections(const DataT x, const DataT y, const DataT z, const Side side, DataT& corrX, DataT& corrY, DataT& corrZ) const
1928{
1929 if (mUseAnaDistCorr) {
1930 getCorrectionsAnalytical(x, y, z, side, corrX, corrY, corrZ);
1931 } else {
1932 // convert cartesian to polar
1933 const DataT radius = getRadiusFromCartesian(x, y);
1934 DataT phi = getPhiFromCartesian(x, y);
1936
1937 DataT corrR{};
1938 DataT corrRPhi{};
1939 getCorrectionsCyl(z, radius, phi, side, corrZ, corrR, corrRPhi);
1940
1941 // Calculate corrected position
1942 const DataT radiusCorr = radius + corrR;
1943 const DataT phiCorr = phi + corrRPhi / radius;
1944
1945 corrX = getXFromPolar(radiusCorr, phiCorr) - x; // difference between corrected and original x coordinate
1946 corrY = getYFromPolar(radiusCorr, phiCorr) - y; // difference between corrected and original y coordinate
1947 }
1948}
1949
1950template <typename DataT>
1951void SpaceCharge<DataT>::getLocalDistortionsCyl(const DataT z, const DataT r, const DataT phi, const Side side, DataT& ldistZ, DataT& ldistR, DataT& ldistRPhi) const
1952{
1953 ldistZ = mInterpolatorLocalDist[side].evaldZ(z, r, phi);
1954 ldistR = mInterpolatorLocalDist[side].evaldR(z, r, phi);
1955 ldistRPhi = mInterpolatorLocalDist[side].evaldRPhi(z, r, phi);
1956}
1957
1958template <typename DataT>
1959void SpaceCharge<DataT>::getLocalDistortionsCyl(const std::vector<DataT>& z, const std::vector<DataT>& r, const std::vector<DataT>& phi, const Side side, std::vector<DataT>& ldistZ, std::vector<DataT>& ldistR, std::vector<DataT>& ldistRPhi) const
1960{
1961 const auto nPoints = z.size();
1962 ldistZ.resize(nPoints);
1963 ldistR.resize(nPoints);
1964 ldistRPhi.resize(nPoints);
1965#pragma omp parallel for num_threads(sNThreads)
1966 for (size_t i = 0; i < nPoints; ++i) {
1967 getLocalDistortionsCyl(z[i], r[i], phi[i], side, ldistZ[i], ldistR[i], ldistRPhi[i]);
1968 }
1969}
1970
1971template <typename DataT>
1972void SpaceCharge<DataT>::getLocalDistortionVectorCyl(const DataT z, const DataT r, const DataT phi, const Side side, DataT& lvecdistZ, DataT& lvecdistR, DataT& lvecdistRPhi) const
1973{
1974 lvecdistZ = mInterpolatorLocalVecDist[side].evaldZ(z, r, phi);
1975 lvecdistR = mInterpolatorLocalVecDist[side].evaldR(z, r, phi);
1976 lvecdistRPhi = mInterpolatorLocalVecDist[side].evaldRPhi(z, r, phi);
1977}
1978
1979template <typename DataT>
1980void SpaceCharge<DataT>::getLocalDistortionVectorCyl(const std::vector<DataT>& z, const std::vector<DataT>& r, const std::vector<DataT>& phi, const Side side, std::vector<DataT>& lvecdistZ, std::vector<DataT>& lvecdistR, std::vector<DataT>& lvecdistRPhi) const
1981{
1982 const auto nPoints = z.size();
1983 lvecdistZ.resize(nPoints);
1984 lvecdistR.resize(nPoints);
1985 lvecdistRPhi.resize(nPoints);
1986#pragma omp parallel for num_threads(sNThreads)
1987 for (size_t i = 0; i < nPoints; ++i) {
1988 getLocalDistortionVectorCyl(z[i], r[i], phi[i], side, lvecdistZ[i], lvecdistR[i], lvecdistRPhi[i]);
1989 }
1990}
1991
1992template <typename DataT>
1993void SpaceCharge<DataT>::getLocalCorrectionVectorCyl(const DataT z, const DataT r, const DataT phi, const Side side, DataT& lveccorrZ, DataT& lveccorrR, DataT& lveccorrRPhi) const
1994{
1995 lveccorrZ = -mInterpolatorLocalVecDist[side].evaldZ(z, r, phi);
1996 lveccorrR = -mInterpolatorLocalVecDist[side].evaldR(z, r, phi);
1997 lveccorrRPhi = -mInterpolatorLocalVecDist[side].evaldRPhi(z, r, phi);
1998}
1999
2000template <typename DataT>
2001void SpaceCharge<DataT>::getLocalCorrectionVectorCyl(const std::vector<DataT>& z, const std::vector<DataT>& r, const std::vector<DataT>& phi, const Side side, std::vector<DataT>& lveccorrZ, std::vector<DataT>& lveccorrR, std::vector<DataT>& lveccorrRPhi) const
2002{
2003 const auto nPoints = z.size();
2004 lveccorrZ.resize(nPoints);
2005 lveccorrR.resize(nPoints);
2006 lveccorrRPhi.resize(nPoints);
2007#pragma omp parallel for num_threads(sNThreads)
2008 for (size_t i = 0; i < nPoints; ++i) {
2009 getLocalCorrectionVectorCyl(z[i], r[i], phi[i], side, lveccorrZ[i], lveccorrR[i], lveccorrRPhi[i]);
2010 }
2011}
2012
2013template <typename DataT>
2014void SpaceCharge<DataT>::getDistortionsCyl(const DataT z, const DataT r, const DataT phi, const Side side, DataT& distZ, DataT& distR, DataT& distRPhi) const
2015{
2016 distZ = mInterpolatorGlobalDist[side].evaldZ(z, r, phi);
2017 distR = mInterpolatorGlobalDist[side].evaldR(z, r, phi);
2018 distRPhi = mInterpolatorGlobalDist[side].evaldRPhi(z, r, phi);
2019}
2020
2021template <typename DataT>
2022void SpaceCharge<DataT>::getDistortionsCyl(const std::vector<DataT>& z, const std::vector<DataT>& r, const std::vector<DataT>& phi, const Side side, std::vector<DataT>& distZ, std::vector<DataT>& distR, std::vector<DataT>& distRPhi) const
2023{
2024 const auto nPoints = z.size();
2025 distZ.resize(nPoints);
2026 distR.resize(nPoints);
2027 distRPhi.resize(nPoints);
2028#pragma omp parallel for num_threads(sNThreads)
2029 for (size_t i = 0; i < nPoints; ++i) {
2030 getDistortionsCyl(z[i], r[i], phi[i], side, distZ[i], distR[i], distRPhi[i]);
2031 }
2032}
2033
2034template <typename DataT>
2035void SpaceCharge<DataT>::getDistortions(const DataT x, const DataT y, const DataT z, const Side side, DataT& distX, DataT& distY, DataT& distZ) const
2036{
2037 DataT zClamped = regulateZ(z, side);
2038
2039 if (mUseAnaDistCorr) {
2040 getDistortionsAnalytical(x, y, zClamped, side, distX, distY, distZ);
2041 } else {
2042 // convert cartesian to polar
2043 const DataT radius = getRadiusFromCartesian(x, y);
2044 const DataT phi = getPhiFromCartesian(x, y);
2045
2046 DataT distR{};
2047 DataT distRPhi{};
2048 DataT rClamped = regulateR(radius, side);
2049 getDistortionsCyl(zClamped, rClamped, phi, side, distZ, distR, distRPhi);
2050
2051 // Calculate distorted position
2052 const DataT radiusDist = rClamped + distR;
2053 const DataT phiDist = phi + distRPhi / rClamped;
2054
2055 distX = getXFromPolar(radiusDist, phiDist) - x; // difference between distorted and original x coordinate
2056 distY = getYFromPolar(radiusDist, phiDist) - y; // difference between distorted and original y coordinate
2057 }
2058}
2059
2060template <typename DataT>
2061void SpaceCharge<DataT>::getDistortionsCorrectionsAnalytical(const DataT x, const DataT y, const DataT z, const Side side, DataT& distX, DataT& distY, DataT& distZ, const bool dist) const
2062{
2063 const GlobalPosition3D pos(x, y, z);
2064 float phi = std::atan2(pos.Y(), pos.X());
2065 if (phi < 0.) {
2066 phi += TWOPI;
2067 }
2068 const unsigned char secNum = std::floor(phi / SECPHIWIDTH);
2069 const Sector sector(secNum + (pos.Z() < 0) * SECTORSPERSIDE);
2070 const LocalPosition3D lPos = Mapper::GlobalToLocal(pos, sector);
2071
2072 // convert dlx and dlY to r, rPhi
2073 const DataT dlX = dist ? mAnaDistCorr.getDistortionsLX(lPos.X(), lPos.Y(), lPos.Z(), side) : mAnaDistCorr.getCorrectionsLX(lPos.X(), lPos.Y(), lPos.Z(), side);
2074 const DataT dlY = dist ? mAnaDistCorr.getDistortionsLY(lPos.X(), lPos.Y(), lPos.Z(), side) : mAnaDistCorr.getCorrectionsLY(lPos.X(), lPos.Y(), lPos.Z(), side);
2075 const DataT dlZ = dist ? mAnaDistCorr.getDistortionsLZ(lPos.X(), lPos.Y(), lPos.Z(), side) : mAnaDistCorr.getCorrectionsLZ(lPos.X(), lPos.Y(), lPos.Z(), side);
2076
2077 // convert distortios in local coordinates to global coordinates
2078 // distorted local position
2079 const LocalPosition3D lPosDist(lPos.X() + dlX, lPos.Y() + dlY, lPos.Z() + dlZ);
2080
2081 // calc global position
2082 const auto globalPosDist = Mapper::LocalToGlobal(lPosDist, sector);
2083 distX = globalPosDist.X() - x;
2084 distY = globalPosDist.Y() - y;
2085 distZ = globalPosDist.Z() - z;
2086
2087 GPUCA_DEBUG_STREAMER_CHECK(if (o2::utils::DebugStreamer::checkStream(o2::utils::StreamFlags::streamDistortionsSC)) {
2088 o2::utils::DebugStreamer::instance()->getStreamer("debug_distortions_analytical", "UPDATE") << o2::utils::DebugStreamer::instance()->getUniqueTreeName("debug_distortions_analytical").data()
2089 << "pos=" << (*const_cast<GlobalPosition3D*>(&pos))
2090 << "lPos=" << (*const_cast<LocalPosition3D*>(&lPos))
2091 << "dlX=" << (*const_cast<DataT*>(&dlX))
2092 << "dlY=" << (*const_cast<DataT*>(&dlY))
2093 << "dlZ=" << (*const_cast<DataT*>(&dlZ))
2094 << "distX=" << distX
2095 << "distY=" << distY
2096 << "distZ=" << distZ
2097 << "\n";
2098 })
2099}
2100
2101template <typename DataT>
2103{
2104 using timer = std::chrono::high_resolution_clock;
2105 if (!mInitLookUpTables) {
2106 auto start = timer::now();
2107 auto o2field = static_cast<o2::field::MagneticField*>(TGeoGlobalMagField::Instance()->GetField());
2108 const float bzField = o2field->solenoidField(); // magnetic field in kGauss
2110 auto& gasParam = ParameterGas::Instance();
2111 float vDrift = gasParam.DriftV; // drift velocity in cm/us
2113 const float t1 = 1.;
2114 const float t2 = 1.;
2116 const float omegaTau = -10. * bzField * vDrift / std::abs(getEzField(Side::A));
2117 setOmegaTauT1T2(omegaTau, t1, t2);
2118 if (mUseInitialSCDensity) {
2119 LOG(warning) << "mUseInitialSCDensity" << mUseInitialSCDensity;
2120 calculateDistortionsCorrections(Side::A);
2121 calculateDistortionsCorrections(Side::C);
2122 mInitLookUpTables = true;
2123 }
2124 auto stop = timer::now();
2125 std::chrono::duration<float> time = stop - start;
2126 LOGP(info, "Total Time Distortions and Corrections for A and C Side: {}", time.count());
2127 }
2128}
2129
2130template <typename DataT>
2131void SpaceCharge<DataT>::setDistortionLookupTables(const DataContainer& distdZ, const DataContainer& distdR, const DataContainer& distdRPhi, const Side side)
2132{
2133 mGlobalDistdR[side] = distdR;
2134 mGlobalDistdZ[side] = distdZ;
2135 mGlobalDistdRPhi[side] = distdRPhi;
2136}
2137
2138template <typename DataT>
2139template <typename Fields>
2140void SpaceCharge<DataT>::integrateEFieldsRoot(const DataT p1r, const DataT p1phi, const DataT p1z, const DataT p2z, DataT& localIntErOverEz, DataT& localIntEPhiOverEz, DataT& localIntDeltaEz, const Fields& formulaStruct, const DataT ezField, const Side side) const
2141{
2142 TF1 fErOverEz(
2143 "fErOverEz", [&](double* x, double* p) { (void)p; return static_cast<double>(formulaStruct.evalFieldR(static_cast<DataT>(x[0]), p1r, p1phi) / (formulaStruct.evalFieldZ(static_cast<DataT>(x[0]), p1r, p1phi) + ezField)); }, p1z, p2z, 1);
2144 localIntErOverEz = static_cast<DataT>(fErOverEz.Integral(p1z, p2z));
2145
2146 TF1 fEphiOverEz(
2147 "fEPhiOverEz", [&](double* x, double* p) { (void)p; return static_cast<double>(formulaStruct.evalFieldPhi(static_cast<DataT>(x[0]), p1r, p1phi) / (formulaStruct.evalFieldZ(static_cast<DataT>(x[0]), p1r, p1phi) + ezField)); }, p1z, p2z, 1);
2148 localIntEPhiOverEz = static_cast<DataT>(fEphiOverEz.Integral(p1z, p2z));
2149
2150 TF1 fEz(
2151 "fEZOverEz", [&](double* x, double* p) { (void)p; return static_cast<double>(formulaStruct.evalFieldZ(static_cast<DataT>(x[0]), p1r, p1phi) - ezField); }, p1z, p2z, 1);
2152 localIntDeltaEz = getSign(side) * static_cast<DataT>(fEz.Integral(p1z, p2z));
2153}
2154
2155template <typename DataT>
2156template <typename Fields>
2157void SpaceCharge<DataT>::integrateEFieldsTrapezoidal(const DataT p1r, const DataT p1phi, const DataT p1z, const DataT p2z, DataT& localIntErOverEz, DataT& localIntEPhiOverEz, DataT& localIntDeltaEz, const Fields& formulaStruct, const DataT ezField, const Side side) const
2158{
2159 //========trapezoidal rule see: https://en.wikipedia.org/wiki/Trapezoidal_rule ==============
2160 const DataT fielder0 = formulaStruct.evalFieldR(p1z, p1r, p1phi);
2161 const DataT fieldez0 = formulaStruct.evalFieldZ(p1z, p1r, p1phi);
2162 const DataT fieldephi0 = formulaStruct.evalFieldPhi(p1z, p1r, p1phi);
2163
2164 const DataT fielder1 = formulaStruct.evalFieldR(p2z, p1r, p1phi);
2165 const DataT fieldez1 = formulaStruct.evalFieldZ(p2z, p1r, p1phi);
2166 const DataT fieldephi1 = formulaStruct.evalFieldPhi(p2z, p1r, p1phi);
2167
2168 const DataT eZ0 = isCloseToZero(ezField, fieldez0) ? 0 : 1. / (ezField + fieldez0);
2169 const DataT eZ1 = isCloseToZero(ezField, fieldez1) ? 0 : 1. / (ezField + fieldez1);
2170
2171 const DataT deltaX = 0.5 * (p2z - p1z);
2172 localIntErOverEz = deltaX * (fielder0 * eZ0 + fielder1 * eZ1);
2173 localIntEPhiOverEz = deltaX * (fieldephi0 * eZ0 + fieldephi1 * eZ1);
2174 localIntDeltaEz = getSign(side) * deltaX * (fieldez0 + fieldez1);
2175}
2176
2177template <typename DataT>
2178template <typename Fields>
2179void SpaceCharge<DataT>::integrateEFieldsSimpson(const DataT p1r, const DataT p1phi, const DataT p1z, const DataT p2z, DataT& localIntErOverEz, DataT& localIntEPhiOverEz, DataT& localIntDeltaEz, const Fields& formulaStruct, const DataT ezField, const Side side) const
2180{
2181 //==========simpsons rule see: https://en.wikipedia.org/wiki/Simpson%27s_rule =============================
2182 const DataT fielder0 = formulaStruct.evalFieldR(p1z, p1r, p1phi);
2183 const DataT fieldez0 = formulaStruct.evalFieldZ(p1z, p1r, p1phi);
2184 const DataT fieldephi0 = formulaStruct.evalFieldPhi(p1z, p1r, p1phi);
2185
2186 const DataT fielder1 = formulaStruct.evalFieldR(p2z, p1r, p1phi);
2187 const DataT fieldez1 = formulaStruct.evalFieldZ(p2z, p1r, p1phi);
2188 const DataT fieldephi1 = formulaStruct.evalFieldPhi(p2z, p1r, p1phi);
2189
2190 const DataT deltaX = p2z - p1z;
2191 const DataT xk2N = (p2z - static_cast<DataT>(0.5) * deltaX);
2192 const DataT ezField2 = formulaStruct.evalFieldZ(xk2N, p1r, p1phi);
2193 const DataT ezField2Denominator = isCloseToZero(ezField, ezField2) ? 0 : 1. / (ezField + ezField2);
2194 const DataT fieldSum2ErOverEz = formulaStruct.evalFieldR(xk2N, p1r, p1phi) * ezField2Denominator;
2195 const DataT fieldSum2EphiOverEz = formulaStruct.evalFieldPhi(xk2N, p1r, p1phi) * ezField2Denominator;
2196
2197 const DataT eZ0 = isCloseToZero(ezField, fieldez0) ? 0 : 1. / (ezField + fieldez0);
2198 const DataT eZ1 = isCloseToZero(ezField, fieldez1) ? 0 : 1. / (ezField + fieldez1);
2199
2200 const DataT deltaXSimpsonSixth = deltaX / 6.;
2201 localIntErOverEz = deltaXSimpsonSixth * (4. * fieldSum2ErOverEz + fielder0 * eZ0 + fielder1 * eZ1);
2202 localIntEPhiOverEz = deltaXSimpsonSixth * (4. * fieldSum2EphiOverEz + fieldephi0 * eZ0 + fieldephi1 * eZ1);
2203 localIntDeltaEz = getSign(side) * deltaXSimpsonSixth * (4. * ezField2 + fieldez0 + fieldez1);
2204}
2205
2206template <typename DataT>
2207template <typename Fields>
2208void SpaceCharge<DataT>::integrateEFieldsSimpsonIterative(const DataT p1r, const DataT p2r, const DataT p1phi, const DataT p2phi, const DataT p1z, const DataT p2z, DataT& localIntErOverEz, DataT& localIntEPhiOverEz, DataT& localIntDeltaEz, const Fields& formulaStruct, const DataT ezField, const Side side) const
2209{
2210 //==========simpsons rule see: https://en.wikipedia.org/wiki/Simpson%27s_rule =============================
2211 // const Side side = formulaStruct.getSide();
2212 // const DataT ezField = getEzField(side);
2213 const DataT p2phiSave = regulatePhi(p2phi, side);
2214
2215 const DataT fielder0 = formulaStruct.evalFieldR(p1z, p1r, p1phi);
2216 const DataT fieldez0 = formulaStruct.evalFieldZ(p1z, p1r, p1phi);
2217 const DataT fieldephi0 = formulaStruct.evalFieldPhi(p1z, p1r, p1phi);
2218
2219 const DataT fielder1 = formulaStruct.evalFieldR(p2z, p2r, p2phiSave);
2220 const DataT fieldez1 = formulaStruct.evalFieldZ(p2z, p2r, p2phiSave);
2221 const DataT fieldephi1 = formulaStruct.evalFieldPhi(p2z, p2r, p2phiSave);
2222
2223 const DataT eZ0Inv = isCloseToZero(ezField, fieldez0) ? 0 : 1. / (ezField + fieldez0);
2224 const DataT eZ1Inv = isCloseToZero(ezField, fieldez1) ? 0 : 1. / (ezField + fieldez1);
2225
2226 const DataT pHalfZ = 0.5 * (p1z + p2z); // dont needs to be regulated since p1z and p2z are already regulated
2227 const DataT pHalfPhiSave = regulatePhi(0.5 * (p1phi + p2phi), side); // needs to be regulated since p2phi is not regulated
2228 const DataT pHalfR = 0.5 * (p1r + p2r);
2229
2230 const DataT ezField2 = formulaStruct.evalFieldZ(pHalfZ, pHalfR, pHalfPhiSave);
2231 const DataT eZHalfInv = (isCloseToZero(ezField, ezField2) | isCloseToZero(ezField, fieldez0) | isCloseToZero(ezField, fieldez1)) ? 0 : 1. / (ezField + ezField2);
2232 const DataT fieldSum2ErOverEz = formulaStruct.evalFieldR(pHalfZ, pHalfR, pHalfPhiSave);
2233 const DataT fieldSum2EphiOverEz = formulaStruct.evalFieldPhi(pHalfZ, pHalfR, pHalfPhiSave);
2234
2235 const DataT deltaXSimpsonSixth = (p2z - p1z) / 6;
2236 localIntErOverEz = deltaXSimpsonSixth * (4 * fieldSum2ErOverEz * eZHalfInv + fielder0 * eZ0Inv + fielder1 * eZ1Inv);
2237 localIntEPhiOverEz = deltaXSimpsonSixth * (4 * fieldSum2EphiOverEz * eZHalfInv + fieldephi0 * eZ0Inv + fieldephi1 * eZ1Inv);
2238 localIntDeltaEz = getSign(side) * deltaXSimpsonSixth * (4 * ezField2 + fieldez0 + fieldez1);
2239}
2240
2241template <typename DataT>
2242std::vector<std::pair<std::vector<o2::math_utils::Point3D<float>>, std::array<DataT, 3>>> SpaceCharge<DataT>::calculateElectronDriftPath(const std::vector<GlobalPosition3D>& elePos, const int nSamplingPoints, const std::string_view outFile) const
2243{
2244 const unsigned int nElectrons = elePos.size();
2245 std::vector<std::pair<std::vector<o2::math_utils::Point3D<float>>, std::array<DataT, 3>>> electronTracks(nElectrons);
2246
2247 for (unsigned int i = 0; i < nElectrons; ++i) {
2248 electronTracks[i].first.reserve(nSamplingPoints + 1);
2249 }
2250
2251 for (unsigned int i = 0; i < nElectrons; ++i) {
2252 const DataT z0 = elePos[i].Z();
2253 const DataT r0 = elePos[i].Rho();
2254 const DataT phi0 = elePos[i].Phi();
2255 const Side side = getSide(z0);
2256 if (!mElectricFieldEr[side].getNDataPoints()) {
2257 LOGP(warning, "E-Fields are not set! Calculation of drift path is not possible");
2258 continue;
2259 }
2260 const NumericalFields<DataT> numEFields{getElectricFieldsInterpolator(side)};
2261 const DataT stepSize = getZMax(side) / nSamplingPoints;
2262
2263 DataT drDist = 0.0; // global distortion dR
2264 DataT dPhiDist = 0.0; // global distortion dPhi (multiplication with R has to be done at the end)
2265 DataT dzDist = 0.0; // global distortion dZ
2266 int iter = 0;
2267 for (;;) {
2268 const DataT z0Tmp = z0 + dzDist + iter * stepSize; // starting z position
2269 const DataT z1Tmp = regulateZ(z0Tmp + stepSize, side); // electron drifts from z0Tmp to z1Tmp
2270 const DataT radius = r0 + drDist; // current radial position of the electron
2271
2272 // abort calculation of drift path if electron reached inner/outer field cage or central electrode
2273 if (radius <= getRMin(side) || radius >= getRMax(side) || getSide(z0Tmp) != side) {
2274 break;
2275 }
2276
2277 const DataT phi = regulatePhi(phi0 + dPhiDist, side); // current phi position of the electron
2278 electronTracks[i].first.emplace_back(GlobalPosition3D(radius * std::cos(phi), radius * std::sin(phi), z0Tmp));
2279
2280 DataT ddR = 0; // distortion dR for drift from z0Tmp to z1Tmp
2281 DataT ddPhi = 0; // distortion dPhi for drift from z0Tmp to z1Tmp
2282 DataT ddZ = 0; // distortion dZ for drift from z0Tmp to z1Tmp
2283
2284 // get the distortion from interpolation of local distortions or calculate distortions with the electric field
2285 processGlobalDistCorr(radius, phi, z0Tmp, z1Tmp, ddR, ddPhi, ddZ, numEFields);
2286
2287 // add local distortions to global distortions
2288 drDist += ddR;
2289 dPhiDist += ddPhi;
2290 dzDist += ddZ;
2291
2292 // if one uses local distortions the interpolated value for the last bin has to be scaled.
2293 // This has to be done because of the interpolated value is defined for a drift length of one z bin, but in the last bin the distance to the readout can be smaller than one z bin.
2294 const bool checkReached = side == Side::A ? z1Tmp >= getZMax(side) : z1Tmp <= getZMax(side);
2295
2296 // set loop to exit if the readout is reached and approximate distortion of 'missing' (one never ends exactly on the readout: z1Tmp + ddZ != ZMAX) drift distance.
2297 // approximation is done by the current calculated values of the distortions and scaled linear to the 'missing' distance.
2298 if (checkReached) {
2299 const DataT endPoint = z1Tmp + ddZ;
2300 const DataT deltaZ = getZMax(side) - endPoint; // distance from last point to read out
2301 const DataT diff = endPoint - z0Tmp;
2302 const DataT fac = diff != 0 ? std::abs(deltaZ / diff) : 0; // approximate the distortions for the 'missing' distance deltaZ
2303 drDist += ddR * fac;
2304 dPhiDist += ddPhi * fac;
2305 dzDist += ddZ * fac;
2306 const DataT z1TmpEnd = regulateZ(z0Tmp + stepSize, side); // electron drifts from z0Tmp to z1Tmp
2307 const DataT radiusEnd = regulateR(r0 + drDist, side); // current radial position of the electron
2308 const DataT phiEnd = regulatePhi(phi0 + dPhiDist, side); // current phi position of the electron
2309 electronTracks[i].first.emplace_back(GlobalPosition3D(radiusEnd * std::cos(phiEnd), radiusEnd * std::sin(phiEnd), z1TmpEnd));
2310 break;
2311 }
2312 ++iter;
2313 }
2314 electronTracks[i].second = std::array<DataT, 3>{drDist, dPhiDist * r0, dzDist};
2315 }
2316 if (!outFile.empty()) {
2317 dumpElectronTracksToTree(electronTracks, nSamplingPoints, outFile.data());
2318 }
2319 return electronTracks;
2320}
2321
2322template <typename DataT>
2323void SpaceCharge<DataT>::dumpElectronTracksToTree(const std::vector<std::pair<std::vector<o2::math_utils::Point3D<float>>, std::array<DataT, 3>>>& electronTracks, const int nSamplingPoints, const char* outFile) const
2324{
2325 o2::utils::TreeStreamRedirector pcstream(outFile, "RECREATE");
2326 pcstream.GetFile()->cd();
2327
2328 auto& gasParam = ParameterGas::Instance();
2329 auto& eleParam = ParameterElectronics::Instance();
2330
2331 for (int i = 0; i < electronTracks.size(); ++i) {
2332 auto electronPath = electronTracks[i].first;
2333 const int nPoints = electronPath.size();
2334 if (electronPath.empty()) {
2335 LOGP(warning, "Track is empty. Continue to next track.");
2336 continue;
2337 }
2338 std::vector<float> relDriftVel;
2339 relDriftVel.reserve(nPoints);
2340
2341 for (int iPoint = 0; iPoint < (nPoints - 2); ++iPoint) {
2342 const DataT relDriftVelTmp = (electronPath[iPoint + 1].Z() - electronPath[iPoint].Z()) / getZMax(getSide(electronPath[iPoint].Z())) * nSamplingPoints; // comparison of drift distance without distortions and with distortions (rel. drift velocity)
2343 relDriftVel.emplace_back(std::abs(relDriftVelTmp));
2344 }
2345
2346 // just copy the last value to avoid wrong values
2347 relDriftVel.emplace_back(relDriftVel.back());
2348 relDriftVel.emplace_back(relDriftVel.back());
2349
2350 DataT distR = electronTracks[i].second[0];
2351 DataT distRPhi = electronTracks[i].second[1];
2352 DataT distZ = electronTracks[i].second[2];
2353
2354 DataT driftTime = std::abs(getZMax(getSide(electronPath.front().Z())) - (distZ + electronPath.front().Z())) / gasParam.DriftV;
2355 DataT timeBin = driftTime / eleParam.ZbinWidth;
2356
2357 pcstream << "drift"
2358 << "electronPath=" << electronPath
2359 << "relDriftVel.=" << relDriftVel // relative drift velocity in z direction
2360 << "distR=" << distR
2361 << "distRPhi=" << distRPhi
2362 << "distZ=" << distZ
2363 << "driftTime=" << driftTime
2364 << "timeBin=" << timeBin
2365 << "\n";
2366 }
2367 pcstream.Close();
2368}
2369
2370template <typename DataT>
2371void SpaceCharge<DataT>::makeElectronDriftPathGif(const char* inpFile, TH2F& hDummy, const int type, const int gifSpeed, const int maxsamplingpoints, const char* outName)
2372{
2373 // read in the tree and convert to vector of std::vector<o2::tpc::GlobalPosition3D>
2374 TFile fInp(inpFile, "READ");
2375 TTree* tree = (TTree*)fInp.Get("drift");
2376 std::vector<o2::tpc::GlobalPosition3D>* electronPathTree = new std::vector<o2::tpc::GlobalPosition3D>;
2377 tree->SetBranchAddress("electronPath", &electronPathTree);
2378
2379 std::vector<std::vector<o2::tpc::GlobalPosition3D>> electronPaths;
2380 std::vector<o2::tpc::GlobalPosition3D> elePosTmp;
2381 const int entries = tree->GetEntriesFast();
2382 for (int i = 0; i < entries; ++i) {
2383 tree->GetEntry(i);
2384 electronPaths.emplace_back(*electronPathTree);
2385 }
2386 delete electronPathTree;
2387 fInp.Close();
2388
2389 TCanvas can("canvas", "canvas", 1000, 600);
2390 can.SetTopMargin(0.04f);
2391 can.SetRightMargin(0.04f);
2392 can.SetBottomMargin(0.12f);
2393 can.SetLeftMargin(0.11f);
2394
2395 const int nElectrons = electronPaths.size();
2396 std::vector<int> indexStartEle(nElectrons);
2397 std::vector<int> countReadoutReached(nElectrons);
2398
2399 // define colors of electrons
2400 const std::vector<int> colorsPalette{kViolet + 2, kViolet + 1, kViolet, kViolet - 1, kGreen + 3, kGreen + 2, kGreen + 1, kOrange - 1, kOrange, kOrange + 1, kOrange + 2, kRed - 1, kRed, kRed + 1, kRed + 2, kBlue - 1, kBlue, kBlue + 1, kBlue + 2};
2401
2402 // create for each electron an individual graph
2403 unsigned int maxPoints = 0;
2404 std::vector<TGraph> gr(nElectrons);
2405 for (int i = 0; i < nElectrons; ++i) {
2406 gr[i].SetMarkerColor(colorsPalette[i % colorsPalette.size()]);
2407
2408 if (electronPaths[i].size() > maxPoints) {
2409 maxPoints = electronPaths[i].size();
2410 }
2411 }
2412
2413 const DataT pointsPerIteration = maxPoints / static_cast<DataT>(maxsamplingpoints);
2414 std::vector<DataT> zRemainder(nElectrons);
2415
2416 for (;;) {
2417 for (auto& graph : gr) {
2418 graph.Set(0);
2419 }
2420
2421 for (int iEle = 0; iEle < nElectrons; ++iEle) {
2422 const int nSamplingPoints = electronPaths[iEle].size();
2423 const int nPoints = std::round(pointsPerIteration + zRemainder[iEle]);
2424 zRemainder[iEle] = pointsPerIteration - nPoints;
2425 const auto& electronPath = electronPaths[iEle];
2426
2427 if (nPoints == 0 && countReadoutReached[iEle] == 0) {
2428 const int indexPoint = indexStartEle[iEle];
2429 const DataT radius = electronPath[indexPoint].Rho();
2430 const DataT z = electronPath[indexPoint].Z();
2431 const DataT phi = electronPath[indexPoint].Phi();
2432 type == 0 ? gr[iEle].AddPoint(z, radius) : gr[iEle].AddPoint(phi, radius);
2433 }
2434
2435 for (int iPoint = 0; iPoint < nPoints; ++iPoint) {
2436 const int indexPoint = indexStartEle[iEle];
2437 if (indexPoint >= nSamplingPoints) {
2438 countReadoutReached[iEle] = 1;
2439 break;
2440 }
2441
2442 const DataT radius = electronPath[indexPoint].Rho();
2443 const DataT z = electronPath[indexPoint].Z();
2444 const DataT phi = electronPath[indexPoint].Phi();
2445 if (iPoint == nPoints / 2) {
2446 type == 0 ? gr[iEle].AddPoint(z, radius) : gr[iEle].AddPoint(phi, radius);
2447 }
2448 ++indexStartEle[iEle];
2449 }
2450 }
2451 hDummy.Draw();
2452 for (auto& graph : gr) {
2453 if (graph.GetN() > 0) {
2454 graph.Draw("P SAME");
2455 }
2456 }
2457 can.Print(Form("%s.gif+%i", outName, gifSpeed));
2458
2459 const int sumReadoutReached = std::accumulate(countReadoutReached.begin(), countReadoutReached.end(), 0);
2460 if (sumReadoutReached == nElectrons) {
2461 break;
2462 }
2463 }
2464
2465 can.Print(Form("%s.gif++", outName));
2466}
2467
2468template <typename DataT>
2469void SpaceCharge<DataT>::dumpToTree(const char* outFileName, const Side side, const int nZPoints, const int nRPoints, const int nPhiPoints, const bool randomize) const
2470{
2471 const DataT phiSpacing = GridProp::getGridSpacingPhi(nPhiPoints) * (MGParameters::normalizeGridToNSector / double(SECTORSPERSIDE));
2472 const DataT rSpacing = GridProp::getGridSpacingR(nRPoints);
2473 const DataT zSpacing = side == Side::A ? GridProp::getGridSpacingZ(nZPoints) : -GridProp::getGridSpacingZ(nZPoints);
2474
2475 std::uniform_real_distribution<DataT> uniR(-rSpacing / 2, rSpacing / 2);
2476 std::uniform_real_distribution<DataT> uniPhi(-phiSpacing / 2, phiSpacing / 2);
2477
2478 std::vector<std::vector<float>> phiPosOut(nPhiPoints);
2479 std::vector<std::vector<float>> rPosOut(nPhiPoints);
2480 std::vector<std::vector<float>> zPosOut(nPhiPoints);
2481 std::vector<std::vector<int>> iPhiOut(nPhiPoints);
2482 std::vector<std::vector<int>> iROut(nPhiPoints);
2483 std::vector<std::vector<int>> iZOut(nPhiPoints);
2484 std::vector<std::vector<float>> densityOut(nPhiPoints);
2485 std::vector<std::vector<float>> potentialOut(nPhiPoints);
2486 std::vector<std::vector<float>> eZOut(nPhiPoints);
2487 std::vector<std::vector<float>> eROut(nPhiPoints);
2488 std::vector<std::vector<float>> ePhiOut(nPhiPoints);
2489 std::vector<std::vector<float>> distZOut(nPhiPoints);
2490 std::vector<std::vector<float>> distROut(nPhiPoints);
2491 std::vector<std::vector<float>> distRPhiOut(nPhiPoints);
2492 std::vector<std::vector<float>> corrZOut(nPhiPoints);
2493 std::vector<std::vector<float>> corrROut(nPhiPoints);
2494 std::vector<std::vector<float>> corrRPhiOut(nPhiPoints);
2495 std::vector<std::vector<float>> lcorrZOut(nPhiPoints);
2496 std::vector<std::vector<float>> lcorrROut(nPhiPoints);
2497 std::vector<std::vector<float>> lcorrRPhiOut(nPhiPoints);
2498 std::vector<std::vector<float>> ldistZOut(nPhiPoints);
2499 std::vector<std::vector<float>> ldistROut(nPhiPoints);
2500 std::vector<std::vector<float>> ldistRPhiOut(nPhiPoints);
2501 std::vector<std::vector<float>> xOut(nPhiPoints);
2502 std::vector<std::vector<float>> yOut(nPhiPoints);
2503 std::vector<std::vector<float>> bROut(nPhiPoints);
2504 std::vector<std::vector<float>> bZOut(nPhiPoints);
2505 std::vector<std::vector<float>> bPhiOut(nPhiPoints);
2506 std::vector<std::vector<LocalPosition3D>> lPosOut(nPhiPoints);
2507 std::vector<std::vector<int>> sectorOut(nPhiPoints);
2508 std::vector<std::vector<size_t>> globalIdxOut(nPhiPoints);
2509 std::vector<std::vector<bool>> isOnPadPlane(nPhiPoints);
2510
2511#pragma omp parallel for num_threads(sNThreads)
2512 for (int iPhi = 0; iPhi < nPhiPoints; ++iPhi) {
2513 const int nPoints = nZPoints * nRPoints;
2514 phiPosOut[iPhi].reserve(nPoints);
2515 rPosOut[iPhi].reserve(nPoints);
2516 zPosOut[iPhi].reserve(nPoints);
2517 iPhiOut[iPhi].reserve(nPoints);
2518 iROut[iPhi].reserve(nPoints);
2519 iZOut[iPhi].reserve(nPoints);
2520 densityOut[iPhi].reserve(nPoints);
2521 potentialOut[iPhi].reserve(nPoints);
2522 eZOut[iPhi].reserve(nPoints);
2523 eROut[iPhi].reserve(nPoints);
2524 ePhiOut[iPhi].reserve(nPoints);
2525 distZOut[iPhi].reserve(nPoints);
2526 distROut[iPhi].reserve(nPoints);
2527 distRPhiOut[iPhi].reserve(nPoints);
2528 corrZOut[iPhi].reserve(nPoints);
2529 corrROut[iPhi].reserve(nPoints);
2530 corrRPhiOut[iPhi].reserve(nPoints);
2531 lcorrZOut[iPhi].reserve(nPoints);
2532 lcorrROut[iPhi].reserve(nPoints);
2533 lcorrRPhiOut[iPhi].reserve(nPoints);
2534 ldistZOut[iPhi].reserve(nPoints);
2535 ldistROut[iPhi].reserve(nPoints);
2536 ldistRPhiOut[iPhi].reserve(nPoints);
2537 xOut[iPhi].reserve(nPoints);
2538 yOut[iPhi].reserve(nPoints);
2539 bROut[iPhi].reserve(nPoints);
2540 bZOut[iPhi].reserve(nPoints);
2541 bPhiOut[iPhi].reserve(nPoints);
2542 lPosOut[iPhi].reserve(nPoints);
2543 sectorOut[iPhi].reserve(nPoints);
2544 globalIdxOut[iPhi].reserve(nPoints);
2545 isOnPadPlane[iPhi].reserve(nPoints);
2546
2547 std::mt19937 rng(std::random_device{}());
2548 DataT phiPos = iPhi * phiSpacing;
2549 for (int iR = 0; iR < nRPoints; ++iR) {
2550 DataT rPos = getRMin(side) + iR * rSpacing;
2551 for (int iZ = 0; iZ < nZPoints; ++iZ) {
2552 DataT zPos = getZMin(side) + iZ * zSpacing;
2553 if (randomize) {
2554 phiPos += uniPhi(rng);
2555 o2::math_utils::detail::bringTo02PiGen<DataT>(phiPos);
2556 rPos += uniR(rng);
2557 }
2558
2559 DataT density = getDensityCyl(zPos, rPos, phiPos, side);
2560 DataT potential = getPotentialCyl(zPos, rPos, phiPos, side);
2561
2562 DataT distZ{};
2563 DataT distR{};
2564 DataT distRPhi{};
2565 getDistortionsCyl(zPos, rPos, phiPos, side, distZ, distR, distRPhi);
2566
2567 DataT ldistZ{};
2568 DataT ldistR{};
2569 DataT ldistRPhi{};
2570 getLocalDistortionsCyl(zPos, rPos, phiPos, side, ldistZ, ldistR, ldistRPhi);
2571
2572 // get average distortions
2573 DataT corrZ{};
2574 DataT corrR{};
2575 DataT corrRPhi{};
2576 // getCorrectionsCyl(zPos, rPos, phiPos, side, corrZ, corrR, corrRPhi);
2577
2578 const DataT zDistorted = zPos + distZ;
2579 const DataT radiusDistorted = rPos + distR;
2580 const DataT phiDistorted = regulatePhi(phiPos + distRPhi / rPos, side);
2581 getCorrectionsCyl(zDistorted, radiusDistorted, phiDistorted, side, corrZ, corrR, corrRPhi);
2582 corrRPhi *= rPos / radiusDistorted;
2583
2584 DataT lcorrZ{};
2585 DataT lcorrR{};
2586 DataT lcorrRPhi{};
2587 getLocalCorrectionsCyl(zPos, rPos, phiPos, side, lcorrZ, lcorrR, lcorrRPhi);
2588
2589 // get average distortions
2590 DataT eZ{};
2591 DataT eR{};
2592 DataT ePhi{};
2593 getElectricFieldsCyl(zPos, rPos, phiPos, side, eZ, eR, ePhi);
2594
2595 // global coordinates
2596 const float x = getXFromPolar(rPos, phiPos);
2597 const float y = getYFromPolar(rPos, phiPos);
2598
2599 // b field
2600 const float bR = mBField.evalFieldR(zPos, rPos, phiPos);
2601 const float bZ = mBField.evalFieldZ(zPos, rPos, phiPos);
2602 const float bPhi = mBField.evalFieldPhi(zPos, rPos, phiPos);
2603
2604 const LocalPosition3D pos(x, y, zPos);
2605 unsigned char secNum = std::floor(phiPos / SECPHIWIDTH);
2606 Sector sector(secNum + (pos.Z() < 0) * SECTORSPERSIDE);
2608
2609 phiPosOut[iPhi].emplace_back(phiPos);
2610 rPosOut[iPhi].emplace_back(rPos);
2611 zPosOut[iPhi].emplace_back(zPos);
2612 iPhiOut[iPhi].emplace_back(iPhi);
2613 iROut[iPhi].emplace_back(iR);
2614 iZOut[iPhi].emplace_back(iZ);
2615 if (mDensity[side].getNDataPoints()) {
2616 densityOut[iPhi].emplace_back(density);
2617 }
2618 if (mPotential[side].getNDataPoints()) {
2619 potentialOut[iPhi].emplace_back(potential);
2620 }
2621 if (mElectricFieldEr[side].getNDataPoints()) {
2622 eZOut[iPhi].emplace_back(eZ);
2623 eROut[iPhi].emplace_back(eR);
2624 ePhiOut[iPhi].emplace_back(ePhi);
2625 }
2626 if (mGlobalDistdR[side].getNDataPoints()) {
2627 distZOut[iPhi].emplace_back(distZ);
2628 distROut[iPhi].emplace_back(distR);
2629 distRPhiOut[iPhi].emplace_back(distRPhi);
2630 }
2631 if (mGlobalCorrdR[side].getNDataPoints()) {
2632 corrZOut[iPhi].emplace_back(corrZ);
2633 corrROut[iPhi].emplace_back(corrR);
2634 corrRPhiOut[iPhi].emplace_back(corrRPhi);
2635 }
2636 if (mLocalCorrdR[side].getNDataPoints()) {
2637 lcorrZOut[iPhi].emplace_back(lcorrZ);
2638 lcorrROut[iPhi].emplace_back(lcorrR);
2639 lcorrRPhiOut[iPhi].emplace_back(lcorrRPhi);
2640 }
2641 if (mLocalDistdR[side].getNDataPoints()) {
2642 ldistZOut[iPhi].emplace_back(ldistZ);
2643 ldistROut[iPhi].emplace_back(ldistR);
2644 ldistRPhiOut[iPhi].emplace_back(ldistRPhi);
2645 }
2646 xOut[iPhi].emplace_back(x);
2647 yOut[iPhi].emplace_back(y);
2648 bROut[iPhi].emplace_back(bR);
2649 bZOut[iPhi].emplace_back(bZ);
2650 bPhiOut[iPhi].emplace_back(bPhi);
2651 lPosOut[iPhi].emplace_back(lPos);
2652 sectorOut[iPhi].emplace_back(sector);
2653 const size_t idx = (iZ + nZPoints * (iR + iPhi * nRPoints));
2654 globalIdxOut[iPhi].emplace_back(idx);
2655
2656 const float xDist = getXFromPolar(radiusDistorted, phiDistorted);
2657 const float yDist = getYFromPolar(radiusDistorted, phiDistorted);
2658 GlobalPosition3D posTmp(xDist, yDist, zPos);
2660 isOnPadPlane[iPhi].emplace_back(digiPadPos.isValid());
2661 }
2662 }
2663 }
2664
2665 if (ROOT::IsImplicitMTEnabled() && (ROOT::GetThreadPoolSize() != sNThreads)) {
2666 ROOT::DisableImplicitMT();
2667 }
2668 ROOT::EnableImplicitMT(sNThreads);
2669 ROOT::RDataFrame dFrame(nPhiPoints);
2670
2671 TStopwatch timer;
2672 auto dfStore = dFrame.DefineSlotEntry("x", [&xOut = xOut](unsigned int, ULong64_t entry) { return xOut[entry]; });
2673 dfStore = dfStore.DefineSlotEntry("y", [&yOut = yOut](unsigned int, ULong64_t entry) { return yOut[entry]; });
2674 dfStore = dfStore.DefineSlotEntry("phi", [&phiPosOut = phiPosOut](unsigned int, ULong64_t entry) { return phiPosOut[entry]; });
2675 dfStore = dfStore.DefineSlotEntry("r", [&rPosOut = rPosOut](unsigned int, ULong64_t entry) { return rPosOut[entry]; });
2676 dfStore = dfStore.DefineSlotEntry("z", [&zPosOut = zPosOut](unsigned int, ULong64_t entry) { return zPosOut[entry]; });
2677 dfStore = dfStore.DefineSlotEntry("iPhi", [&iPhiOut = iPhiOut](unsigned int, ULong64_t entry) { return iPhiOut[entry]; });
2678 dfStore = dfStore.DefineSlotEntry("iR", [&iROut = iROut](unsigned int, ULong64_t entry) { return iROut[entry]; });
2679 dfStore = dfStore.DefineSlotEntry("iZ", [&iZOut = iZOut](unsigned int, ULong64_t entry) { return iZOut[entry]; });
2680 dfStore = dfStore.DefineSlotEntry("lPos", [&lPosOut = lPosOut](unsigned int, ULong64_t entry) { return lPosOut[entry]; });
2681 dfStore = dfStore.DefineSlotEntry("sector", [&sectorOut = sectorOut](unsigned int, ULong64_t entry) { return sectorOut[entry]; });
2682 dfStore = dfStore.DefineSlotEntry("scdensity", [&densityOut = densityOut](unsigned int, ULong64_t entry) { return densityOut[entry]; });
2683 dfStore = dfStore.DefineSlotEntry("potential", [&potentialOut = potentialOut](unsigned int, ULong64_t entry) { return potentialOut[entry]; });
2684 dfStore = dfStore.DefineSlotEntry("eZ", [&eZOut = eZOut](unsigned int, ULong64_t entry) { return eZOut[entry]; });
2685 dfStore = dfStore.DefineSlotEntry("eR", [&eROut = eROut](unsigned int, ULong64_t entry) { return eROut[entry]; });
2686 dfStore = dfStore.DefineSlotEntry("ePhi", [&ePhiOut = ePhiOut](unsigned int, ULong64_t entry) { return ePhiOut[entry]; });
2687 dfStore = dfStore.DefineSlotEntry("distZ", [&distZOut = distZOut](unsigned int, ULong64_t entry) { return distZOut[entry]; });
2688 dfStore = dfStore.DefineSlotEntry("distR", [&distROut = distROut](unsigned int, ULong64_t entry) { return distROut[entry]; });
2689 dfStore = dfStore.DefineSlotEntry("distRPhi", [&distRPhiOut = distRPhiOut](unsigned int, ULong64_t entry) { return distRPhiOut[entry]; });
2690 dfStore = dfStore.DefineSlotEntry("corrZ", [&corrZOut = corrZOut](unsigned int, ULong64_t entry) { return corrZOut[entry]; });
2691 dfStore = dfStore.DefineSlotEntry("corrR", [&corrROut = corrROut](unsigned int, ULong64_t entry) { return corrROut[entry]; });
2692 dfStore = dfStore.DefineSlotEntry("corrRPhi", [&corrRPhiOut = corrRPhiOut](unsigned int, ULong64_t entry) { return corrRPhiOut[entry]; });
2693 dfStore = dfStore.DefineSlotEntry("lcorrZ", [&lcorrZOut = lcorrZOut](unsigned int, ULong64_t entry) { return lcorrZOut[entry]; });
2694 dfStore = dfStore.DefineSlotEntry("lcorrR", [&lcorrROut = lcorrROut](unsigned int, ULong64_t entry) { return lcorrROut[entry]; });
2695 dfStore = dfStore.DefineSlotEntry("lcorrRPhi", [&lcorrRPhiOut = lcorrRPhiOut](unsigned int, ULong64_t entry) { return lcorrRPhiOut[entry]; });
2696 dfStore = dfStore.DefineSlotEntry("ldistZ", [&ldistZOut = ldistZOut](unsigned int, ULong64_t entry) { return ldistZOut[entry]; });
2697 dfStore = dfStore.DefineSlotEntry("ldistR", [&ldistROut = ldistROut](unsigned int, ULong64_t entry) { return ldistROut[entry]; });
2698 dfStore = dfStore.DefineSlotEntry("ldistRPhi", [&ldistRPhiOut = ldistRPhiOut](unsigned int, ULong64_t entry) { return ldistRPhiOut[entry]; });
2699 dfStore = dfStore.DefineSlotEntry("bR", [&bROut = bROut](unsigned int, ULong64_t entry) { return bROut[entry]; });
2700 dfStore = dfStore.DefineSlotEntry("bZ", [&bZOut = bZOut](unsigned int, ULong64_t entry) { return bZOut[entry]; });
2701 dfStore = dfStore.DefineSlotEntry("bPhi", [&bPhiOut = bPhiOut](unsigned int, ULong64_t entry) { return bPhiOut[entry]; });
2702 dfStore = dfStore.DefineSlotEntry("globalIndex", [&globalIdxOut = globalIdxOut](unsigned int, ULong64_t entry) { return globalIdxOut[entry]; });
2703 dfStore = dfStore.DefineSlotEntry("isOnPadPlane", [&isOnPadPlane = isOnPadPlane](unsigned int, ULong64_t entry) { return isOnPadPlane[entry]; });
2704 dfStore.Snapshot("tree", outFileName);
2705 timer.Print("u");
2706}
2707
2708template <typename DataT>
2709void SpaceCharge<DataT>::dumpToTree(const char* outFileName, const Sector& sector, const int nZPoints) const
2710{
2711 const Side side = sector.side();
2712 const DataT zSpacing = (side == Side::A) ? GridProp::getGridSpacingZ(nZPoints) : -GridProp::getGridSpacingZ(nZPoints);
2713 const Mapper& mapper = Mapper::instance();
2714
2715 const int nPads = Mapper::getPadsInSector();
2716 std::vector<std::vector<float>> phiPosOut(nZPoints);
2717 std::vector<std::vector<float>> rPosOut(nZPoints);
2718 std::vector<std::vector<float>> zPosOut(nZPoints);
2719 std::vector<std::vector<int>> rowOut(nZPoints);
2720 std::vector<std::vector<float>> lxOut(nZPoints);
2721 std::vector<std::vector<float>> lyOut(nZPoints);
2722 std::vector<std::vector<float>> xOut(nZPoints);
2723 std::vector<std::vector<float>> yOut(nZPoints);
2724 std::vector<std::vector<float>> corrZOut(nZPoints);
2725 std::vector<std::vector<float>> corrROut(nZPoints);
2726 std::vector<std::vector<float>> corrRPhiOut(nZPoints);
2727 std::vector<std::vector<float>> erOut(nZPoints);
2728 std::vector<std::vector<float>> ezOut(nZPoints);
2729 std::vector<std::vector<float>> ephiOut(nZPoints);
2730 std::vector<std::vector<float>> potentialOut(nZPoints);
2731 std::vector<std::vector<int>> izOut(nZPoints);
2732 std::vector<std::vector<size_t>> globalIdxOut(nZPoints);
2733
2734#pragma omp parallel for num_threads(sNThreads)
2735 for (int iZ = 0; iZ < nZPoints; ++iZ) {
2736 phiPosOut[iZ].reserve(nPads);
2737 rPosOut[iZ].reserve(nPads);
2738 zPosOut[iZ].reserve(nPads);
2739 corrZOut[iZ].reserve(nPads);
2740 corrROut[iZ].reserve(nPads);
2741 corrRPhiOut[iZ].reserve(nPads);
2742 rowOut[iZ].reserve(nPads);
2743 lxOut[iZ].reserve(nPads);
2744 lyOut[iZ].reserve(nPads);
2745 xOut[iZ].reserve(nPads);
2746 yOut[iZ].reserve(nPads);
2747 erOut[iZ].reserve(nPads);
2748 ezOut[iZ].reserve(nPads);
2749 ephiOut[iZ].reserve(nPads);
2750 izOut[iZ].reserve(nPads);
2751 potentialOut[iZ].reserve(nPads);
2752 globalIdxOut[iZ].reserve(nPads);
2753
2754 DataT zPos = getZMin(side) + iZ * zSpacing;
2755 for (unsigned int region = 0; region < Mapper::NREGIONS; ++region) {
2756 for (unsigned int irow = 0; irow < Mapper::ROWSPERREGION[region]; ++irow) {
2757 for (unsigned int ipad = 0; ipad < Mapper::PADSPERROW[region][irow]; ++ipad) {
2758 GlobalPadNumber globalpad = Mapper::getGlobalPadNumber(irow, ipad, region);
2759 const PadCentre& padcentre = mapper.padCentre(globalpad);
2760 auto lx = padcentre.X();
2761 auto ly = padcentre.Y();
2762 // local to global
2763 auto globalPos = Mapper::LocalToGlobal(padcentre, sector);
2764 auto x = globalPos.X();
2765 auto y = globalPos.Y();
2766
2767 auto r = getRadiusFromCartesian(x, y);
2768 auto phi = getPhiFromCartesian(x, y);
2769 DataT corrZ{};
2770 DataT corrR{};
2771 DataT corrRPhi{};
2772 getCorrectionsCyl(zPos, r, phi, side, corrZ, corrR, corrRPhi);
2773
2774 DataT eZ{};
2775 DataT eR{};
2776 DataT ePhi{};
2777 getElectricFieldsCyl(zPos, r, phi, side, eZ, eR, ePhi);
2778
2779 potentialOut[iZ].emplace_back(getPotentialCyl(zPos, r, phi, side));
2780 erOut[iZ].emplace_back(eR);
2781 ezOut[iZ].emplace_back(eZ);
2782 ephiOut[iZ].emplace_back(ePhi);
2783 phiPosOut[iZ].emplace_back(phi);
2784 rPosOut[iZ].emplace_back(r);
2785 zPosOut[iZ].emplace_back(zPos);
2786 corrZOut[iZ].emplace_back(corrZ);
2787 corrROut[iZ].emplace_back(corrR);
2788 corrRPhiOut[iZ].emplace_back(corrRPhi);
2789 rowOut[iZ].emplace_back(irow + Mapper::ROWOFFSET[region]);
2790 lxOut[iZ].emplace_back(lx);
2791 lyOut[iZ].emplace_back(ly);
2792 xOut[iZ].emplace_back(x);
2793 yOut[iZ].emplace_back(y);
2794 izOut[iZ].emplace_back(iZ);
2795 const size_t idx = globalpad + Mapper::getPadsInSector() * iZ;
2796 globalIdxOut[iZ].emplace_back(idx);
2797 }
2798 }
2799 }
2800 }
2801
2802 if (ROOT::IsImplicitMTEnabled() && (ROOT::GetThreadPoolSize() != sNThreads)) {
2803 ROOT::DisableImplicitMT();
2804 }
2805 ROOT::EnableImplicitMT(sNThreads);
2806 ROOT::RDataFrame dFrame(nZPoints);
2807
2808 TStopwatch timer;
2809 auto dfStore = dFrame.DefineSlotEntry("phi", [&phiPosOut = phiPosOut](unsigned int, ULong64_t entry) { return phiPosOut[entry]; });
2810 dfStore = dfStore.DefineSlotEntry("r", [&rPosOut = rPosOut](unsigned int, ULong64_t entry) { return rPosOut[entry]; });
2811 dfStore = dfStore.DefineSlotEntry("z", [&zPosOut = zPosOut](unsigned int, ULong64_t entry) { return zPosOut[entry]; });
2812 dfStore = dfStore.DefineSlotEntry("iz", [&izOut = izOut](unsigned int, ULong64_t entry) { return izOut[entry]; });
2813 dfStore = dfStore.DefineSlotEntry("corrZ", [&corrZOut = corrZOut](unsigned int, ULong64_t entry) { return corrZOut[entry]; });
2814 dfStore = dfStore.DefineSlotEntry("corrR", [&corrROut = corrROut](unsigned int, ULong64_t entry) { return corrROut[entry]; });
2815 dfStore = dfStore.DefineSlotEntry("corrRPhi", [&corrRPhiOut = corrRPhiOut](unsigned int, ULong64_t entry) { return corrRPhiOut[entry]; });
2816 dfStore = dfStore.DefineSlotEntry("row", [&rowOut = rowOut](unsigned int, ULong64_t entry) { return rowOut[entry]; });
2817 dfStore = dfStore.DefineSlotEntry("lx", [&lxOut = lxOut](unsigned int, ULong64_t entry) { return lxOut[entry]; });
2818 dfStore = dfStore.DefineSlotEntry("ly", [&lyOut = lyOut](unsigned int, ULong64_t entry) { return lyOut[entry]; });
2819 dfStore = dfStore.DefineSlotEntry("x", [&xOut = xOut](unsigned int, ULong64_t entry) { return xOut[entry]; });
2820 dfStore = dfStore.DefineSlotEntry("y", [&yOut = yOut](unsigned int, ULong64_t entry) { return yOut[entry]; });
2821 dfStore = dfStore.DefineSlotEntry("er", [&erOut = erOut](unsigned int, ULong64_t entry) { return erOut[entry]; });
2822 dfStore = dfStore.DefineSlotEntry("ez", [&ezOut = ezOut](unsigned int, ULong64_t entry) { return ezOut[entry]; });
2823 dfStore = dfStore.DefineSlotEntry("ephi", [&ephiOut = ephiOut](unsigned int, ULong64_t entry) { return ephiOut[entry]; });
2824 dfStore = dfStore.DefineSlotEntry("potential", [&potentialOut = potentialOut](unsigned int, ULong64_t entry) { return potentialOut[entry]; });
2825 dfStore = dfStore.DefineSlotEntry("globalIndex", [&globalIdxOut = globalIdxOut](unsigned int, ULong64_t entry) { return globalIdxOut[entry]; });
2826 dfStore.Snapshot("tree", outFileName);
2827 timer.Print("u");
2828}
2829
2830template <typename DataT>
2832{
2833 const auto deltaPhi = histoIonsPhiRZ.GetXaxis()->GetBinWidth(1);
2834 const auto deltaZ = histoIonsPhiRZ.GetZaxis()->GetBinWidth(1);
2835 const auto fac = deltaPhi * deltaZ * o2::tpc::TPCParameters<DataT>::E0 / (2 * 100 * TMath::Qe()); // 100 to normalize to cm: vacuum permittivity [A·s/(V·cm)]
2836 for (int ir = 1; ir <= histoIonsPhiRZ.GetNbinsY(); ++ir) {
2837 const auto r0 = histoIonsPhiRZ.GetYaxis()->GetBinLowEdge(ir);
2838 const auto r1 = histoIonsPhiRZ.GetYaxis()->GetBinUpEdge(ir);
2839 const auto norm = fac * (r1 * r1 - r0 * r0);
2840 for (int iphi = 1; iphi <= histoIonsPhiRZ.GetNbinsX(); ++iphi) {
2841 for (int iz = 1; iz <= histoIonsPhiRZ.GetNbinsZ(); ++iz) {
2842 const auto charge = histoIonsPhiRZ.GetBinContent(iphi, ir, iz);
2843 histoIonsPhiRZ.SetBinContent(iphi, ir, iz, charge / norm);
2844 }
2845 }
2846 }
2847}
2848
2849template <typename DataT>
2851{
2852 if (!mAnaDistCorr.isValid()) {
2853 LOGP(info, "============== analytical functions are not set! returning ==============");
2854 return 0;
2855 }
2856 bool isOK = outf.WriteObject(&mAnaDistCorr, "analyticalDistCorr");
2857 return isOK;
2858}
2859
2860template <typename DataT>
2862{
2863 TFile fIn(inpf.data(), "READ");
2864 const bool containsFormulas = fIn.GetListOfKeys()->Contains("analyticalDistCorr");
2865 if (!containsFormulas) {
2866 LOGP(info, "============== analytical functions are not stored! returning ==============");
2867 return;
2868 }
2869 LOGP(info, "Using analytical corrections and distortions");
2870 setUseAnalyticalDistCorr(true);
2871 AnalyticalDistCorr<DataT>* form = (AnalyticalDistCorr<DataT>*)fIn.Get("analyticalDistCorr");
2872 mAnaDistCorr = *form;
2873 delete form;
2874}
2875
2876template <typename DataT>
2877void SpaceCharge<DataT>::langevinCylindricalE(DataT& ddR, DataT& ddPhi, DataT& ddZ, const DataT radius, const DataT localIntErOverEz, const DataT localIntEPhiOverEz, const DataT localIntDeltaEz) const
2878{
2879 // calculated distortions/correction with the formula described in https://edms.cern.ch/ui/file/1108138/1/ALICE-INT-2010-016.pdf page 7.
2880 ddR = mC0 * localIntErOverEz + mC1 * localIntEPhiOverEz;
2881 ddPhi = (mC0 * localIntEPhiOverEz - mC1 * localIntErOverEz) / radius;
2882 ddZ = -localIntDeltaEz * TPCParameters<DataT>::DVDE;
2883}
2884
2885template <typename DataT>
2886void SpaceCharge<DataT>::langevinCylindricalB(DataT& ddR, DataT& ddPhi, const DataT radius, const DataT localIntBrOverBz, const DataT localIntBPhiOverBz) const
2887{
2888 // calculated distortions/correction with the formula described in https://edms.cern.ch/ui/file/1108138/1/ALICE-INT-2010-016.pdf page 7.
2889 ddR = mC2 * localIntBrOverBz - mC1 * localIntBPhiOverBz;
2890 ddPhi = (mC2 * localIntBPhiOverBz + mC1 * localIntBrOverBz) / radius;
2891}
2892
2893template <typename DataT>
2895{
2896 const std::unordered_map<int, std::pair<int, int>> field_to_current = {{2, {12000, 6000}},
2897 {5, {30000, 6000}},
2898 {-2, {-12000, -6000}},
2899 {-5, {-30000, -6000}},
2900 {0, {0, 0}}};
2901 auto currents_iter = field_to_current.find(field);
2902 if (currents_iter == field_to_current.end()) {
2903 LOG(error) << " Could not lookup currents for fieldvalue " << field;
2904 return;
2905 }
2906 mBField.setBField(field);
2908 magField.setL3Current((*currents_iter).second.first);
2909 magField.setDipoleCurrent((*currents_iter).second.second);
2910 setBFields(magField);
2911}
2912
2913template <typename DataT>
2915{
2916 const float bzField = int(magField.getNominalL3Field());
2917 auto& gasParam = ParameterGas::Instance();
2918 float vDrift = gasParam.DriftV; // drift velocity in cm/us
2919 const float omegaTau = -10. * bzField * vDrift / std::abs(getEzField(Side::A));
2920 LOGP(detail, "Setting omegaTau to {} for {}kG", omegaTau, bzField);
2921 const float t1 = 1.;
2922 const float t2 = 1.;
2923 setOmegaTauT1T2(omegaTau, t1, t2);
2924}
2925
2926template <typename DataT>
2927template <typename Fields>
2928void SpaceCharge<DataT>::calcDistCorr(const DataT p1r, const DataT p1phi, const DataT p1z, const DataT p2z, DataT& ddR, DataT& ddPhi, DataT& ddZ, const Fields& formulaStruct, const bool localDistCorr, const Side side) const
2929{
2930 // see: https://edms.cern.ch/ui/file/1108138/1/ALICE-INT-2010-016.pdf
2931 // needed for calculation of distortions/corrections
2932 DataT localIntErOverEz = 0; // integral_p1z^p2z Er/Ez dz
2933 DataT localIntEPhiOverEz = 0; // integral_p1z^p2z Ephi/Ez dz
2934 DataT localIntDeltaEz = 0; // integral_p1z^p2z Ez dz
2935
2936 DataT localIntBrOverBz = 0; // integral_p1z^p2z Br/Bz dz
2937 DataT localIntBPhiOverBz = 0; // integral_p1z^p2z Bphi/Bz dz
2938 DataT localIntDeltaBz = 0; // integral_p1z^p2z Bz dz
2939 DataT ddRExB = 0;
2940 DataT ddPhiExB = 0;
2941
2942 // there are differentnumerical integration strategys implements. for details see each function.
2943 switch (sNumericalIntegrationStrategy) {
2944 case IntegrationStrategy::SimpsonIterative: // iterative simpson integration (should be more precise at least for the analytical E-Field case but takes alot more time than normal simpson integration)
2945 for (int i = 0; i < sSimpsonNIteratives; ++i) { // TODO define a convergence criterion to abort the algorithm earlier for speed up.
2946 const DataT tmpZ = localDistCorr ? (p2z + ddZ) : regulateZ(p2z + ddZ, formulaStruct.getSide()); // dont regulate for local distortions/corrections! (to get same result as using electric field at last/first bin)
2947 if (mSimEDistortions) {
2948 integrateEFieldsSimpsonIterative(p1r, p1r + ddR + ddRExB, p1phi, p1phi + ddPhi + ddPhiExB, p1z, tmpZ, localIntErOverEz, localIntEPhiOverEz, localIntDeltaEz, formulaStruct, getEzField(side), side);
2949 langevinCylindricalE(ddR, ddPhi, ddZ, (p1r + 0.5 * (ddR + ddRExB)), localIntErOverEz, localIntEPhiOverEz, localIntDeltaEz); // using the mean radius '(p1r + 0.5 * ddR)' for calculation of distortions/corections
2950 }
2951 if (mSimExBMisalignment) {
2952 integrateEFieldsSimpsonIterative(p1r, p1r + ddR + ddRExB, p1phi, p1phi + ddPhi + ddPhiExB, p1z, tmpZ, localIntBrOverBz, localIntBPhiOverBz, localIntDeltaBz, mBField, 0, side);
2953 langevinCylindricalB(ddRExB, ddPhiExB, (p1r + 0.5 * (ddR + ddRExB)), localIntBrOverBz, localIntBPhiOverBz);
2954 }
2955 }
2956 break;
2957 case IntegrationStrategy::Simpson: // simpson integration
2958 if (mSimEDistortions) {
2959 integrateEFieldsSimpson(p1r, p1phi, p1z, p2z, localIntErOverEz, localIntEPhiOverEz, localIntDeltaEz, formulaStruct, getEzField(side), side);
2960 langevinCylindricalE(ddR, ddPhi, ddZ, p1r, localIntErOverEz, localIntEPhiOverEz, localIntDeltaEz);
2961 }
2962 if (mSimExBMisalignment) {
2963 integrateEFieldsSimpson(p1r, p1phi, p1z, p2z, localIntBrOverBz, localIntBPhiOverBz, localIntDeltaBz, mBField, 0, side);
2964 langevinCylindricalB(ddRExB, ddPhiExB, p1r, localIntBrOverBz, localIntBPhiOverBz);
2965 }
2966 break;
2967 case IntegrationStrategy::Trapezoidal: // trapezoidal integration (fastest)
2968 if (mSimEDistortions) {
2969 integrateEFieldsTrapezoidal(p1r, p1phi, p1z, p2z, localIntErOverEz, localIntEPhiOverEz, localIntDeltaEz, formulaStruct, getEzField(side), side);
2970 langevinCylindricalE(ddR, ddPhi, ddZ, p1r, localIntErOverEz, localIntEPhiOverEz, localIntDeltaEz);
2971 }
2972 if (mSimExBMisalignment) {
2973 integrateEFieldsTrapezoidal(p1r, p1phi, p1z, p2z, localIntBrOverBz, localIntBPhiOverBz, localIntDeltaBz, mBField, 0, side);
2974 langevinCylindricalB(ddRExB, ddPhiExB, p1r, localIntBrOverBz, localIntBPhiOverBz);
2975 }
2976 break;
2977 case IntegrationStrategy::Root: // using integration implemented in ROOT (slow)
2978 if (mSimEDistortions) {
2979 integrateEFieldsRoot(p1r, p1phi, p1z, p2z, localIntErOverEz, localIntEPhiOverEz, localIntDeltaEz, formulaStruct, getEzField(side), side);
2980 langevinCylindricalE(ddR, ddPhi, ddZ, p1r, localIntErOverEz, localIntEPhiOverEz, localIntDeltaEz);
2981 }
2982 if (mSimExBMisalignment) {
2983 integrateEFieldsRoot(p1r, p1phi, p1z, p2z, localIntBrOverBz, localIntBPhiOverBz, localIntDeltaBz, mBField, 0, side);
2984 langevinCylindricalB(ddRExB, ddPhiExB, p1r, localIntBrOverBz, localIntBPhiOverBz);
2985 }
2986 break;
2987 default:
2988 if (mSimEDistortions) {
2989 integrateEFieldsSimpson(p1r, p1phi, p1z, p2z, localIntErOverEz, localIntEPhiOverEz, localIntDeltaEz, formulaStruct, getEzField(side), side);
2990 langevinCylindricalE(ddR, ddPhi, ddZ, p1r, localIntErOverEz, localIntEPhiOverEz, localIntDeltaEz);
2991 }
2992 if (mSimExBMisalignment) {
2993 integrateEFieldsSimpson(p1r, p1phi, p1z, p2z, localIntBrOverBz, localIntBPhiOverBz, localIntDeltaBz, mBField, 0, side);
2994 langevinCylindricalB(ddRExB, ddPhiExB, p1r, localIntBrOverBz, localIntBPhiOverBz);
2995 }
2996 }
2997
2998 GPUCA_DEBUG_STREAMER_CHECK(if (o2::utils::DebugStreamer::checkStream(o2::utils::StreamFlags::streamDistortionsSC)) {
2999 o2::utils::DebugStreamer::instance()->getStreamer("debug_calcDistCorr", "UPDATE") << o2::utils::DebugStreamer::instance()->getUniqueTreeName("debug_calcDistCorr").data()
3000 << "p1r=" << (*const_cast<DataT*>(&p1r))
3001 << "p1phi=" << (*const_cast<DataT*>(&p1phi))
3002 << "p1z=" << (*const_cast<DataT*>(&p1z))
3003 << "p2z=" << (*const_cast<DataT*>(&p2z))
3004 << "localIntErOverEz=" << localIntErOverEz
3005 << "localIntEPhiOverEz=" << localIntEPhiOverEz
3006 << "localIntDeltaEz=" << localIntDeltaEz
3007 << "ddR=" << ddR
3008 << "ddPhi=" << ddPhi
3009 << "ddZ=" << ddZ
3010 << "localIntBrOverBz=" << localIntBrOverBz
3011 << "localIntBPhiOverBz=" << localIntBPhiOverBz
3012 << "localIntDeltaBz=" << localIntDeltaBz
3013 << "ddRExB=" << ddRExB
3014 << "ddPhiExB=" << ddPhiExB
3015 << "\n";
3016 })
3017
3018 ddR += ddRExB;
3019 ddPhi += ddPhiExB;
3020}
3021
3022template <typename DataT>
3023int SpaceCharge<DataT>::dumpElectricFields(std::string_view file, const Side side, std::string_view option) const
3024{
3025 if (!mElectricFieldEr[side].getNDataPoints()) {
3026 LOGP(info, "============== E-Fields are not set! returning ==============");
3027 return 0;
3028 }
3029 const std::string sideName = getSideName(side);
3030 const int er = mElectricFieldEr[side].writeToFile(file, option, fmt::format("fieldEr_side{}", sideName), sNThreads);
3031 const int ez = mElectricFieldEz[side].writeToFile(file, "UPDATE", fmt::format("fieldEz_side{}", sideName), sNThreads);
3032 const int ephi = mElectricFieldEphi[side].writeToFile(file, "UPDATE", fmt::format("fieldEphi_side{}", sideName), sNThreads);
3033 dumpMetaData(file, "UPDATE", false);
3034 return er + ez + ephi;
3035}
3036
3037template <typename DataT>
3039{
3040 const std::string sideName = getSideName(side);
3041 std::string_view treeEr{fmt::format("fieldEr_side{}", sideName)};
3042 if (!checkGridFromFile(file, treeEr)) {
3043 return;
3044 }
3045 initContainer(mElectricFieldEr[side], true);
3046 initContainer(mElectricFieldEz[side], true);
3047 initContainer(mElectricFieldEphi[side], true);
3048 mElectricFieldEr[side].initFromFile(file, treeEr, sNThreads);
3049 mElectricFieldEz[side].initFromFile(file, fmt::format("fieldEz_side{}", sideName), sNThreads);
3050 mElectricFieldEphi[side].initFromFile(file, fmt::format("fieldEphi_side{}", sideName), sNThreads);
3051 readMetaData(file);
3052}
3053
3054template <typename DataT>
3055int SpaceCharge<DataT>::dumpGlobalDistortions(std::string_view file, const Side side, std::string_view option) const
3056{
3057 if (!mGlobalDistdR[side].getNDataPoints()) {
3058 LOGP(info, "============== global distortions are not set! returning ==============");
3059 return 0;
3060 }
3061 const std::string sideName = getSideName(side);
3062 const int er = mGlobalDistdR[side].writeToFile(file, option, fmt::format("distR_side{}", sideName), sNThreads);
3063 const int ez = mGlobalDistdZ[side].writeToFile(file, "UPDATE", fmt::format("distZ_side{}", sideName), sNThreads);
3064 const int ephi = mGlobalDistdRPhi[side].writeToFile(file, "UPDATE", fmt::format("distRphi_side{}", sideName), sNThreads);
3065 dumpMetaData(file, "UPDATE", false);
3066 return er + ez + ephi;
3067}
3068
3069template <typename DataT>
3071{
3072 const std::string sideName = getSideName(side);
3073 std::string_view tree{fmt::format("distR_side{}", sideName)};
3074 if (!checkGridFromFile(file, tree)) {
3075 return;
3076 }
3077 initContainer(mGlobalDistdR[side], true);
3078 initContainer(mGlobalDistdZ[side], true);
3079 initContainer(mGlobalDistdRPhi[side], true);
3080 mGlobalDistdR[side].initFromFile(file, tree, sNThreads);
3081 mGlobalDistdZ[side].initFromFile(file, fmt::format("distZ_side{}", sideName), sNThreads);
3082 mGlobalDistdRPhi[side].initFromFile(file, fmt::format("distRphi_side{}", sideName), sNThreads);
3083 readMetaData(file);
3084}
3085
3086template <typename DataT>
3087template <typename DataTIn>
3089{
3090 initContainer(mGlobalDistdR[side], false);
3091 initContainer(mGlobalDistdZ[side], false);
3092 initContainer(mGlobalDistdRPhi[side], false);
3093 const std::string sideName = getSideName(side);
3094 mGlobalDistdR[side].template initFromFile<DataTIn>(inpf, fmt::format("distR_side{}", sideName).data());
3095 mGlobalDistdZ[side].template initFromFile<DataTIn>(inpf, fmt::format("distZ_side{}", sideName).data());
3096 mGlobalDistdRPhi[side].template initFromFile<DataTIn>(inpf, fmt::format("distRphi_side{}", sideName).data());
3097}
3098
3099template <typename DataT>
3100int SpaceCharge<DataT>::dumpGlobalCorrections(std::string_view file, const Side side, std::string_view option) const
3101{
3102 if (!mGlobalCorrdR[side].getNDataPoints()) {
3103 LOGP(info, "============== global corrections are not set! returning ==============");
3104 return 0;
3105 }
3106 const std::string sideName = getSideName(side);
3107 const int er = mGlobalCorrdR[side].writeToFile(file, option, fmt::format("corrR_side{}", sideName), sNThreads);
3108 const int ez = mGlobalCorrdZ[side].writeToFile(file, "UPDATE", fmt::format("corrZ_side{}", sideName), sNThreads);
3109 const int ephi = mGlobalCorrdRPhi[side].writeToFile(file, "UPDATE", fmt::format("corrRPhi_side{}", sideName), sNThreads);
3110 dumpMetaData(file, "UPDATE", false);
3111 return er + ez + ephi;
3112}
3113
3114template <typename DataT>
3116{
3117 const std::string sideName = getSideName(side);
3118 const std::string_view treename{fmt::format("corrR_side{}", getSideName(side))};
3119 if (!checkGridFromFile(file, treename)) {
3120 return;
3121 }
3122
3123 initContainer(mGlobalCorrdR[side], true);
3124 initContainer(mGlobalCorrdZ[side], true);
3125 initContainer(mGlobalCorrdRPhi[side], true);
3126 mGlobalCorrdR[side].initFromFile(file, treename, sNThreads);
3127 mGlobalCorrdZ[side].initFromFile(file, fmt::format("corrZ_side{}", sideName), sNThreads);
3128 mGlobalCorrdRPhi[side].initFromFile(file, fmt::format("corrRPhi_side{}", sideName), sNThreads);
3129 readMetaData(file);
3130}
3131
3132template <typename DataT>
3133template <typename DataTIn>
3135{
3136 initContainer(mGlobalCorrdR[side], false);
3137 initContainer(mGlobalCorrdZ[side], false);
3138 initContainer(mGlobalCorrdRPhi[side], false);
3139 const std::string sideName = getSideName(side);
3140 mGlobalCorrdR[side].template initFromFile<DataTIn>(inpf, fmt::format("corrR_side{}", sideName).data());
3141 mGlobalCorrdZ[side].template initFromFile<DataTIn>(inpf, fmt::format("corrZ_side{}", sideName).data());
3142 mGlobalCorrdRPhi[side].template initFromFile<DataTIn>(inpf, fmt::format("corrRPhi_side{}", sideName).data());
3143}
3144
3145template <typename DataT>
3146int SpaceCharge<DataT>::dumpLocalCorrections(std::string_view file, const Side side, std::string_view option) const
3147{
3148 if (!mLocalCorrdR[side].getNDataPoints()) {
3149 LOGP(info, "============== local corrections are not set! returning ==============");
3150 return 0;
3151 }
3152 const std::string sideName = getSideName(side);
3153 const int lCorrdR = mLocalCorrdR[side].writeToFile(file, option, fmt::format("lcorrR_side{}", sideName), sNThreads);
3154 const int lCorrdZ = mLocalCorrdZ[side].writeToFile(file, "UPDATE", fmt::format("lcorrZ_side{}", sideName), sNThreads);
3155 const int lCorrdRPhi = mLocalCorrdRPhi[side].writeToFile(file, "UPDATE", fmt::format("lcorrRPhi_side{}", sideName), sNThreads);
3156 dumpMetaData(file, "UPDATE", false);
3157 return lCorrdR + lCorrdZ + lCorrdRPhi;
3158}
3159
3160template <typename DataT>
3162{
3163 const std::string sideName = getSideName(side);
3164 const std::string_view treename{fmt::format("lcorrR_side{}", getSideName(side))};
3165 if (!checkGridFromFile(file, treename)) {
3166 return;
3167 }
3168 initContainer(mLocalCorrdR[side], true);
3169 initContainer(mLocalCorrdZ[side], true);
3170 initContainer(mLocalCorrdRPhi[side], true);
3171 const bool lCorrdR = mLocalCorrdR[side].initFromFile(file, treename, sNThreads);
3172 const bool lCorrdZ = mLocalCorrdZ[side].initFromFile(file, fmt::format("lcorrZ_side{}", sideName), sNThreads);
3173 const bool lCorrdRPhi = mLocalCorrdRPhi[side].initFromFile(file, fmt::format("lcorrRPhi_side{}", sideName), sNThreads);
3174 readMetaData(file);
3175}
3176
3177template <typename DataT>
3178int SpaceCharge<DataT>::dumpLocalDistortions(std::string_view file, const Side side, std::string_view option) const
3179{
3180 if (!mLocalDistdR[side].getNDataPoints()) {
3181 LOGP(info, "============== local distortions are not set! returning ==============");
3182 return 0;
3183 }
3184 const std::string sideName = getSideName(side);
3185 const int lDistdR = mLocalDistdR[side].writeToFile(file, option, fmt::format("ldistR_side{}", sideName), sNThreads);
3186 const int lDistdZ = mLocalDistdZ[side].writeToFile(file, "UPDATE", fmt::format("ldistZ_side{}", sideName), sNThreads);
3187 const int lDistdRPhi = mLocalDistdRPhi[side].writeToFile(file, "UPDATE", fmt::format("ldistRPhi_side{}", sideName), sNThreads);
3188 dumpMetaData(file, "UPDATE", false);
3189 return lDistdR + lDistdZ + lDistdRPhi;
3190}
3191
3192template <typename DataT>
3193int SpaceCharge<DataT>::dumpLocalDistCorrVectors(std::string_view file, const Side side, std::string_view option) const
3194{
3195 if (!mLocalVecDistdR[side].getNDataPoints()) {
3196 LOGP(info, "============== local distortion vectors are not set! returning ==============");
3197 return 0;
3198 }
3199 const std::string sideName = getSideName(side);
3200 const int lVecDistdR = mLocalVecDistdR[side].writeToFile(file, option, fmt::format("lvecdistR_side{}", sideName), sNThreads);
3201 const int lVecDistdZ = mLocalVecDistdZ[side].writeToFile(file, "UPDATE", fmt::format("lvecdistZ_side{}", sideName), sNThreads);
3202 const int lVecDistdRPhi = mLocalVecDistdRPhi[side].writeToFile(file, "UPDATE", fmt::format("lvecdistRPhi_side{}", sideName), sNThreads);
3203 dumpMetaData(file, "UPDATE", false);
3204 return lVecDistdR + lVecDistdZ + lVecDistdRPhi;
3205}
3206
3207template <typename DataT>
3209{
3210 const std::string sideName = getSideName(side);
3211 const std::string_view treename{fmt::format("ldistR_side{}", getSideName(side))};
3212 if (!checkGridFromFile(file, treename)) {
3213 return;
3214 }
3215 initContainer(mLocalDistdR[side], true);
3216 initContainer(mLocalDistdZ[side], true);
3217 initContainer(mLocalDistdRPhi[side], true);
3218 const bool lDistdR = mLocalDistdR[side].initFromFile(file, treename, sNThreads);
3219 const bool lDistdZ = mLocalDistdZ[side].initFromFile(file, fmt::format("ldistZ_side{}", sideName), sNThreads);
3220 const bool lDistdRPhi = mLocalDistdRPhi[side].initFromFile(file, fmt::format("ldistRPhi_side{}", sideName), sNThreads);
3221 readMetaData(file);
3222}
3223
3224template <typename DataT>
3226{
3227 const std::string sideName = getSideName(side);
3228 const std::string_view treename{fmt::format("lvecdistR_side{}", getSideName(side))};
3229 if (!checkGridFromFile(file, treename)) {
3230 return;
3231 }
3232 initContainer(mLocalVecDistdR[side], true);
3233 initContainer(mLocalVecDistdZ[side], true);
3234 initContainer(mLocalVecDistdRPhi[side], true);
3235 const bool lVecDistdR = mLocalVecDistdR[side].initFromFile(file, treename, sNThreads);
3236 const bool lVecDistdZ = mLocalVecDistdZ[side].initFromFile(file, fmt::format("lvecdistZ_side{}", sideName), sNThreads);
3237 const bool lVecDistdRPhi = mLocalVecDistdRPhi[side].initFromFile(file, fmt::format("lvecdistRPhi_side{}", sideName), sNThreads);
3238 readMetaData(file);
3239}
3240
3241template <typename DataT>
3242int SpaceCharge<DataT>::dumpPotential(std::string_view file, const Side side, std::string_view option) const
3243{
3244 if (!mPotential[side].getNDataPoints()) {
3245 LOGP(info, "============== potential not set! returning ==============");
3246 return 0;
3247 }
3248 int status = mPotential[side].writeToFile(file, option, fmt::format("potential_side{}", getSideName(side)), sNThreads);
3249 dumpMetaData(file, "UPDATE", false);
3250 return status;
3251}
3252
3253template <typename DataT>
3255{
3256 const std::string_view treename{fmt::format("potential_side{}", getSideName(side))};
3257 if (!checkGridFromFile(file, treename)) {
3258 return;
3259 }
3260 initContainer(mPotential[side], true);
3261 mPotential[side].initFromFile(file, treename, sNThreads);
3262 readMetaData(file);
3263}
3264
3265template <typename DataT>
3266int SpaceCharge<DataT>::dumpDensity(std::string_view file, const Side side, std::string_view option) const
3267{
3268 if (!mDensity[side].getNDataPoints()) {
3269 LOGP(info, "============== space charge density are not set! returning ==============");
3270 return 0;
3271 }
3272 int status = mDensity[side].writeToFile(file, option, fmt::format("density_side{}", getSideName(side)), sNThreads);
3273 dumpMetaData(file, "UPDATE", false);
3274 return status;
3275}
3276
3277template <typename DataT>
3278bool SpaceCharge<DataT>::checkGridFromFile(std::string_view file, std::string_view tree)
3279{
3280 unsigned short nr, nz, nphi;
3281 if (!DataContainer::getVertices(tree, file, nr, nz, nphi)) {
3282 return false;
3283 }
3284
3285 // check if stored grid definition and current grid definition is the same
3286 if ((mParamGrid.NRVertices != nr) || (mParamGrid.NZVertices != nz) || (mParamGrid.NPhiVertices != nphi)) {
3287 LOGP(info, "Different number of vertices found in input file. Initializing new space charge object with nR {} nZ {} nPhi {} vertices", nr, nz, nphi);
3288 SpaceCharge<DataT> scTmp(mBField.getBField(), nz, nr, nphi, false);
3289 scTmp.mC0 = mC0;
3290 scTmp.mC1 = mC1;
3291 scTmp.mC2 = mC2;
3292 *this = std::move(scTmp);
3293 }
3294 return true;
3295}
3296
3297template <typename DataT>
3299{
3300 const std::string_view treename{fmt::format("density_side{}", getSideName(side))};
3301 if (!checkGridFromFile(file, treename)) {
3302 return;
3303 }
3304 initContainer(mDensity[side], true);
3305 mDensity[side].initFromFile(file, treename, sNThreads);
3306 readMetaData(file);
3307}
3308
3309template <typename DataT>
3311{
3312 if (!mGlobalCorrdR[side].getNDataPoints()) {
3313 LOGP(info, "============== global corrections are not set! returning ==============");
3314 return 0;
3315 }
3316 const std::string sideName = getSideName(side);
3317 const int er = mGlobalCorrdR[side].template writeToFile<float>(outf, fmt::format("corrR_side{}", sideName).data());
3318 const int ez = mGlobalCorrdZ[side].template writeToFile<float>(outf, fmt::format("corrZ_side{}", sideName).data());
3319 const int ephi = mGlobalCorrdRPhi[side].template writeToFile<float>(outf, fmt::format("corrRPhi_side{}", sideName).data());
3320 return er + ez + ephi;
3321}
3322
3323template <typename DataT>
3324void SpaceCharge<DataT>::dumpToFile(std::string_view file, const Side side, std::string_view option) const
3325{
3326 if (option == "RECREATE") {
3327 // delete the file
3328 gSystem->Unlink(file.data());
3329 }
3330 dumpElectricFields(file, side, "UPDATE");
3331 dumpPotential(file, side, "UPDATE");
3332 dumpDensity(file, side, "UPDATE");
3333 dumpGlobalDistortions(file, side, "UPDATE");
3334 dumpGlobalCorrections(file, side, "UPDATE");
3335 dumpLocalCorrections(file, side, "UPDATE");
3336 dumpLocalDistortions(file, side, "UPDATE");
3337 dumpLocalDistCorrVectors(file, side, "UPDATE");
3338}
3339
3340template <typename DataT>
3341void SpaceCharge<DataT>::dumpToFile(std::string_view file) const
3342{
3343 dumpToFile(file, Side::A, "RECREATE");
3344 dumpToFile(file, Side::C, "UPDATE");
3345}
3346
3347template <typename DataT>
3348void SpaceCharge<DataT>::dumpMetaData(std::string_view file, std::string_view option, const bool overwriteExisting) const
3349{
3350 TFile f(file.data(), option.data());
3351 if (!overwriteExisting && f.GetListOfKeys()->Contains("meta")) {
3352 return;
3353 }
3354 f.Close();
3355
3356 // create meta objects
3357 std::vector<float> params{static_cast<float>(mC0), static_cast<float>(mC1), static_cast<float>(mC2)};
3358 auto helperA = mGrid3D[Side::A].getHelper();
3359 auto helperC = mGrid3D[Side::C].getHelper();
3360
3361 // define dataframe
3362 ROOT::RDataFrame dFrame(1);
3363 auto dfStore = dFrame.DefineSlotEntry("paramsC", [&params = params](unsigned int, ULong64_t entry) { return params; });
3364 dfStore = dfStore.DefineSlotEntry("grid_A", [&helperA = helperA](unsigned int, ULong64_t entry) { return helperA; });
3365 dfStore = dfStore.DefineSlotEntry("grid_C", [&helperC = helperC](unsigned int, ULong64_t entry) { return helperC; });
3366 dfStore = dfStore.DefineSlotEntry("BField", [field = mBField.getBField()](unsigned int, ULong64_t entry) { return field; });
3367 dfStore = dfStore.DefineSlotEntry("metaInf", [meta = mMeta](unsigned int, ULong64_t entry) { return meta; });
3368
3369 // write to TTree
3370 ROOT::RDF::RSnapshotOptions opt;
3371 opt.fMode = option;
3372 opt.fOverwriteIfExists = true; // overwrite if already exists
3373 dfStore.Snapshot("meta", file, {"paramsC", "grid_A", "grid_C", "BField", "metaInf"}, opt);
3374}
3375
3376template <typename DataT>
3378{
3379 if (mReadMetaData) {
3380 return;
3381 }
3382
3383 // check if TTree exists
3384 TFile f(file.data(), "READ");
3385 if (!f.GetListOfKeys()->Contains("meta")) {
3386 return;
3387 }
3388 f.Close();
3389
3390 auto readMeta = [&mC0 = mC0, &mC1 = mC1, &mC2 = mC2, &mGrid3D = mGrid3D, &mBField = mBField](const std::vector<float>& paramsC, const RegularGridHelper<double>& gridA, const RegularGridHelper<double>& gridC, int field) {
3391 mC0 = paramsC[0];
3392 mC1 = paramsC[1];
3393 mC2 = paramsC[2];
3394 mGrid3D[Side::A] = RegularGrid3D<DataT>(gridA.zmin, gridA.rmin, gridA.phimin, gridA.spacingZ, gridA.spacingR, gridA.spacingPhi, gridA.params);
3395 mGrid3D[Side::C] = RegularGrid3D<DataT>(gridC.zmin, gridC.rmin, gridC.phimin, gridC.spacingZ, gridC.spacingR, gridC.spacingPhi, gridC.params);
3396 mBField.setBField(field);
3397 };
3398
3399 ROOT::RDataFrame dFrame("meta", file);
3400 dFrame.Foreach(readMeta, {"paramsC", "grid_A", "grid_C", "BField"});
3401
3402 const auto& cols = dFrame.GetColumnNames();
3403 if (std::find(cols.begin(), cols.end(), "metaInf") != cols.end()) {
3404 auto readMetaInf = [&mMeta = mMeta](const SCMetaData& meta) {
3405 mMeta = meta;
3406 };
3407 dFrame.Foreach(readMetaInf, {"metaInf"});
3408 }
3409
3410 LOGP(info, "Setting meta data: mC0={} mC1={} mC2={}", mC0, mC1, mC2);
3411 mReadMetaData = true;
3412}
3413
3414template <typename DataT>
3416{
3417 LOGP(warning, "Use this feature only if you know what you are doing!");
3419 RegularGrid gridTmp[FNSIDES]{{GridProp::ZMIN, GridProp::RMIN, GridProp::PHIMIN, getSign(Side::A) * GridProp::getGridSpacingZ(mParamGrid.NZVertices), GridProp::getGridSpacingR(mParamGrid.NRVertices), GridProp::getGridSpacingPhi(mParamGrid.NPhiVertices) / SECTORSPERSIDE * nSectors, mParamGrid},
3420 {GridProp::ZMIN, GridProp::RMIN, GridProp::PHIMIN, getSign(Side::C) * GridProp::getGridSpacingZ(mParamGrid.NZVertices), GridProp::getGridSpacingR(mParamGrid.NRVertices), GridProp::getGridSpacingPhi(mParamGrid.NPhiVertices) / SECTORSPERSIDE * nSectors, mParamGrid}};
3421 mGrid3D[0] = gridTmp[0];
3422 mGrid3D[1] = gridTmp[1];
3423}
3424
3425template <typename DataT>
3430
3431template <typename DataT>
3432void SpaceCharge<DataT>::setFromFile(std::string_view file, const Side side)
3433{
3434 setDensityFromFile(file, side);
3435 setPotentialFromFile(file, side);
3436 setElectricFieldsFromFile(file, side);
3437 setLocalDistortionsFromFile(file, side);
3438 setLocalCorrectionsFromFile(file, side);
3439 setGlobalDistortionsFromFile(file, side);
3440 setGlobalCorrectionsFromFile(file, side);
3441 setLocalDistCorrVectorsFromFile(file, side);
3442}
3443
3444template <typename DataT>
3446{
3447 setFromFile(file, Side::A);
3448 setFromFile(file, Side::C);
3449}
3450
3451template <typename DataT>
3452void SpaceCharge<DataT>::initContainer(DataContainer& data, const bool initMem)
3453{
3454 if (!data.getNDataPoints()) {
3455 data.setGrid(getNZVertices(), getNRVertices(), getNPhiVertices(), initMem);
3456 }
3457}
3458
3459template <typename DataT>
3460void SpaceCharge<DataT>::setOmegaTauT1T2(const DataT omegaTau, const DataT t1, const DataT t2)
3461{
3462 const DataT wt0 = t2 * omegaTau;
3463 const DataT wt02 = wt0 * wt0;
3464 mC0 = 1 / (1 + wt02);
3465 const DataT wt1 = t1 * omegaTau;
3466 mC1 = wt1 / (1 + wt1 * wt1);
3467 mC2 = wt02 / (1 + wt02);
3468}
3469
3470template <typename DataT>
3472{
3473 const bool sameGrid = (getNPhiVertices() == otherSC.getNPhiVertices()) && (getNRVertices() == otherSC.getNRVertices()) && (getNZVertices() == otherSC.getNZVertices());
3474 if (!sameGrid) {
3475 LOGP(warning, "Space charge objects have different grid definition");
3476 return;
3477 }
3478
3479 mDensity[Side::A] += otherSC.mDensity[Side::A];
3480 mDensity[Side::C] += otherSC.mDensity[Side::C];
3481}
3482
3483template <typename DataT>
3485{
3486 const bool sameGrid = (getNPhiVertices() == otherSC.getNPhiVertices()) && (getNRVertices() == otherSC.getNRVertices()) && (getNZVertices() == otherSC.getNZVertices());
3487 if (!sameGrid) {
3488 LOGP(warning, "Space charge objects have different grid definition");
3489 return;
3490 }
3491
3492 mGlobalCorrdR[side] += otherSC.mGlobalCorrdR[side];
3493 mGlobalCorrdZ[side] += otherSC.mGlobalCorrdZ[side];
3494 mGlobalCorrdRPhi[side] += otherSC.mGlobalCorrdRPhi[side];
3495}
3496
3497template <typename DataT>
3498void SpaceCharge<DataT>::fillChargeDensityFromHisto(const char* file, const char* nameA, const char* nameC)
3499{
3500 TFile fInp(file, "READ");
3501 TH3F* hSCA = (TH3F*)fInp.Get(nameA);
3502 TH3F* hSCC = (TH3F*)fInp.Get(nameC);
3503 if (!hSCA) {
3504 LOGP(error, "Histogram {} not found", nameA);
3505 }
3506 if (!hSCC) {
3507 LOGP(error, "Histogram {} not found", nameC);
3508 }
3509 fillChargeDensityFromHisto(*hSCA, *hSCC);
3510}
3511
3512template <typename DataT>
3513void SpaceCharge<DataT>::fillChargeDensityFromHisto(const TH3& hisSCDensity3D_A, const TH3& hisSCDensity3D_C)
3514{
3515 const int nPhiBinsTmp = hisSCDensity3D_A.GetXaxis()->GetNbins();
3516 const int nRBinsTmp = hisSCDensity3D_A.GetYaxis()->GetNbins();
3517 const int nZBins = hisSCDensity3D_A.GetZaxis()->GetNbins();
3518 const auto phiLow = hisSCDensity3D_A.GetXaxis()->GetBinLowEdge(1);
3519 const auto phiUp = hisSCDensity3D_A.GetXaxis()->GetBinUpEdge(nPhiBinsTmp);
3520 const auto rLow = hisSCDensity3D_A.GetYaxis()->GetBinLowEdge(1);
3521 const auto rUp = hisSCDensity3D_A.GetYaxis()->GetBinUpEdge(nRBinsTmp);
3522 const auto zUp = hisSCDensity3D_A.GetZaxis()->GetBinUpEdge(nZBins);
3523
3524 TH3F hisSCMerged("hisMerged", "hisMerged", nPhiBinsTmp, phiLow, phiUp, nRBinsTmp, rLow, rUp, 2 * nZBins, -zUp, zUp);
3525
3526 for (int iside = 0; iside < FNSIDES; ++iside) {
3527 const auto& hSC = (iside == 0) ? hisSCDensity3D_A : hisSCDensity3D_C;
3528#pragma omp parallel for num_threads(sNThreads)
3529 for (int iz = 1; iz <= nZBins; ++iz) {
3530 const int izTmp = (iside == 0) ? (nZBins + iz) : iz;
3531 for (int ir = 1; ir <= nRBinsTmp; ++ir) {
3532 for (int iphi = 1; iphi <= nPhiBinsTmp; ++iphi) {
3533 hisSCMerged.SetBinContent(iphi, ir, izTmp, hSC.GetBinContent(iphi, ir, iz));
3534 }
3535 }
3536 }
3537 }
3538 fillChargeDensityFromHisto(hisSCMerged);
3539}
3540
3541template <typename DataT>
3542void SpaceCharge<DataT>::convertIDCsToCharge(std::vector<CalDet<float>>& idcZero, const CalDet<float>& mapIBF, const float ionDriftTimeMS, const bool normToPadArea)
3543{
3544 // 1. integration time per IDC interval in ms
3545 const int nOrbits = 12;
3546 const float idcIntegrationTimeMS = (nOrbits * o2::constants::lhc::LHCOrbitMUS) / 1e3;
3547
3548 // number of time stamps for each integration interval (5346) (see: IDCSim.h getNTimeStampsPerIntegrationInterval())
3549 const unsigned int nTimeStampsPerIDCInterval{(o2::constants::lhc::LHCMaxBunches * nOrbits) / o2::tpc::constants::LHCBCPERTIMEBIN};
3550
3551 const int nIDCSlices = idcZero.size();
3552 // IDCs are normalized for each interval to 5346 time bins
3553 // IDC0 = <IDC> per ms = 5346 / ~1ms
3554 const float idcsPerMS = nTimeStampsPerIDCInterval / idcIntegrationTimeMS;
3555
3556 // length of one z slice on ms
3557 const float lengthZSliceMS = ionDriftTimeMS / nIDCSlices;
3558
3559 // IDCs for one z slice
3560 const float scaleToIonDrift = lengthZSliceMS * idcsPerMS;
3561
3562 // get conversion factor from ADC to electrons (see: SAMPAProcessing::getADCvalue())
3563 const static ParameterElectronics& parameterElectronics = ParameterElectronics::Instance();
3564 const float conversionADCToEle = parameterElectronics.ElectronCharge * 1.e15 * parameterElectronics.ChipGain * parameterElectronics.ADCsaturation / parameterElectronics.ADCdynamicRange;
3565
3566 const float conversionFactor = scaleToIonDrift / conversionADCToEle;
3567 LOGP(info, "Converting IDCs to space-charge density with conversion factor of {}", conversionFactor);
3568
3569 for (auto& calIDC : idcZero) {
3570 if (normToPadArea) {
3571 for (unsigned int sector = 0; sector < Mapper::NSECTORS; ++sector) {
3572 for (unsigned int region = 0; region < Mapper::NREGIONS; ++region) {
3573 for (int lrow = 0; lrow < Mapper::ROWSPERREGION[region]; ++lrow) {
3574 for (unsigned int pad = 0; pad < Mapper::PADSPERROW[region][lrow]; ++pad) {
3575 const int globalPad = Mapper::getGlobalPadNumber(lrow, pad, region);
3576 float idcTmp = calIDC.getValue(sector, globalPad);
3577 calIDC.setValue(sector, globalPad, conversionFactor * idcTmp / Mapper::INVPADAREA[region]);
3578 }
3579 }
3580 }
3581 }
3582 } else {
3583 calIDC *= conversionFactor;
3584 }
3585 // take IBF into account
3586 calIDC *= mapIBF;
3587 calIDC *= 0.01f; // ibf values are in %
3588 }
3589}
3590
3591template <typename DataT>
3592void SpaceCharge<DataT>::fillChargeFromIDCs(std::vector<CalDet<float>>& idcZero, const CalDet<float>& mapIBF, const float ionDriftTimeMS, const bool normToPadArea)
3593{
3594 convertIDCsToCharge(idcZero, mapIBF, ionDriftTimeMS, normToPadArea);
3595 fillChargeFromCalDet(idcZero);
3596}
3597
3598template <typename DataT>
3599void SpaceCharge<DataT>::initRodAlignmentVoltages(const MisalignmentType misalignmentType, const FCType fcType, const int sector, const Side side, const float deltaPot)
3600{
3601 // see also original implementation in AliTPCFCVoltError3D::InitFCVoltError3D (https://github.com/alisw/AliRoot/blob/master/TPC/TPCbase/AliTPCFCVoltError3D.h)
3602
3603 const int iside = static_cast<int>(side);
3604 initContainer(mPotential[iside], true);
3605 const int phiVerticesPerSector = mParamGrid.NPhiVertices / SECTORSPERSIDE;
3606 int phiVerticesEnd = phiVerticesPerSector;
3607
3608 if (misalignmentType == MisalignmentType::RodShift) {
3609 const float rodDiameter = 1; // 1 cm SET SOMEWEHERE ELSE
3610 const float rodRadius = rodDiameter / 2;
3611 const float radiusTmp = (fcType == FCType::IFC) ? TPCParameters<DataT>::IFCRADIUS : TPCParameters<DataT>::OFCRADIUS;
3612 int nPhiVerticesPerRod = static_cast<int>(rodRadius * mParamGrid.NPhiVertices / (TWOPI * radiusTmp) + 0.5);
3613 if (nPhiVerticesPerRod == 0) {
3614 nPhiVerticesPerRod = 1;
3615 }
3616 phiVerticesEnd = nPhiVerticesPerRod;
3617 }
3618
3619 const int phiStart = sector * phiVerticesPerSector;
3620 const int phiEnd = phiStart + phiVerticesEnd;
3621 const int nRVertex = (fcType == FCType::IFC) ? 0 : (mParamGrid.NRVertices - 1);
3622 for (size_t iPhi = phiStart; iPhi < phiEnd; ++iPhi) {
3623 const int iPhiSector = iPhi % phiVerticesPerSector;
3624
3625 float potentialSector = 0;
3626 float potentialLastSector = 0;
3627 if ((misalignmentType == MisalignmentType::ShiftedClip) || (misalignmentType == MisalignmentType::RodShift)) {
3628 const float potentialShiftedClips = deltaPot - iPhiSector * deltaPot / phiVerticesEnd;
3629 potentialSector = potentialShiftedClips;
3630 potentialLastSector = potentialShiftedClips;
3631 } else if (misalignmentType == MisalignmentType::RotatedClip) {
3632 // set to zero for first vertex
3633 if (iPhiSector == 0) {
3634 potentialSector = 0;
3635 potentialLastSector = 0;
3636 } else {
3637 const float potentialRotatedClip = -deltaPot + (iPhiSector - 1) * deltaPot / (phiVerticesPerSector - 2);
3638 potentialSector = potentialRotatedClip;
3639 potentialLastSector = -potentialRotatedClip;
3640 }
3641 }
3642
3643 for (size_t iZ = 0; iZ < mParamGrid.NZVertices; ++iZ) {
3644 mPotential[iside](iZ, nRVertex, iPhi) += potentialSector;
3645 if (iPhiSector > 0) {
3646 const int iPhiMirror = ((phiStart - iPhiSector) + mParamGrid.NPhiVertices) % mParamGrid.NPhiVertices;
3647 mPotential[iside](iZ, nRVertex, iPhiMirror) += potentialLastSector;
3648 }
3649 }
3650 }
3651}
3652
3653template <typename DataT>
3655{
3656 if (other.mPotential[side].getData().empty()) {
3657 LOGP(info, "Other space-charge object is empty!");
3658 return;
3659 }
3660
3661 if ((mParamGrid.NRVertices != other.mParamGrid.NRVertices) || (mParamGrid.NZVertices != other.mParamGrid.NZVertices) || (mParamGrid.NPhiVertices != other.mParamGrid.NPhiVertices)) {
3662 LOGP(info, "Different number of vertices found in input file. Initializing new space charge object with nR {} nZ {} nPhi {} vertices", other.mParamGrid.NRVertices, other.mParamGrid.NZVertices, other.mParamGrid.NPhiVertices);
3663 SpaceCharge<DataT> scTmp(mBField.getBField(), other.mParamGrid.NZVertices, other.mParamGrid.NRVertices, other.mParamGrid.NPhiVertices, false);
3664 scTmp.mC0 = mC0;
3665 scTmp.mC1 = mC1;
3666 scTmp.mC2 = mC2;
3667 *this = std::move(scTmp);
3668 }
3669
3670 initContainer(mPotential[side], true);
3671
3672 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
3673 for (size_t iZ = 1; iZ < mParamGrid.NZVertices; ++iZ) {
3674 const size_t iRFirst = 0;
3675 mPotential[side](iZ, iRFirst, iPhi) += scaling * other.mPotential[side](iZ, iRFirst, iPhi);
3676
3677 const size_t iRLast = mParamGrid.NRVertices - 1;
3678 mPotential[side](iZ, iRLast, iPhi) += scaling * other.mPotential[side](iZ, iRLast, iPhi);
3679 }
3680 }
3681
3682 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
3683 for (size_t iR = 0; iR < mParamGrid.NRVertices; ++iR) {
3684 const size_t iZFirst = 0;
3685 mPotential[side](iZFirst, iR, iPhi) += scaling * other.mPotential[side](iZFirst, iR, iPhi);
3686
3687 const size_t iZLast = mParamGrid.NZVertices - 1;
3688 mPotential[side](iZLast, iR, iPhi) += scaling * other.mPotential[side](iZLast, iR, iPhi);
3689 }
3690 }
3691}
3692
3693template <typename DataT>
3695{
3696 const float zMaxAbs = std::abs(zMax);
3697 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
3698 for (size_t iZ = 1; iZ < mParamGrid.NZVertices; ++iZ) {
3699 const DataT z = std::abs(getZVertex(iZ, side));
3700 if ((z < zMin) || (z > zMax)) {
3701 const size_t iRFirst = 0;
3702 mPotential[side](iZ, iRFirst, iPhi) = 0;
3703
3704 const size_t iRLast = mParamGrid.NRVertices - 1;
3705 mPotential[side](iZ, iRLast, iPhi) = 0;
3706 }
3707 }
3708 }
3709
3710 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
3711 for (size_t iR = 0; iR < mParamGrid.NRVertices; ++iR) {
3712 const size_t iZFirst = 0;
3713 const float zFirst = std::abs(getZVertex(iZFirst, side));
3714 if ((zFirst < zMin) || (zFirst > zMax)) {
3715 mPotential[side](iZFirst, iR, iPhi) = 0;
3716 }
3717
3718 const size_t iZLast = mParamGrid.NZVertices - 1;
3719 const float zLast = std::abs(getZVertex(iZLast, side));
3720 if ((zLast < zMin) || (zLast > zMax)) {
3721 mPotential[side](iZLast, iR, iPhi) = 0;
3722 }
3723 }
3724 }
3725}
3726
3727template <typename DataT>
3728void SpaceCharge<DataT>::setIFCChargeUpRisingPot(const float deltaPot, const float zMaxDeltaPot, const int type, const float zStart, const float offs, const Side side)
3729{
3730 std::function<DataT(DataT)> chargeUpIFCLinear = [zStart, type, offs, deltaPot, zMaxDeltaPot](const DataT z) {
3731 const float absZ = std::abs(z);
3732 const float absZMaxDeltaPot = std::abs(zMaxDeltaPot);
3733 if ((absZ <= absZMaxDeltaPot) && (absZ >= zStart)) {
3734 // 1/x
3735 if (type == 1) {
3736 const float offsZ = 1;
3737 const float zMaxDeltaPotTmp = zMaxDeltaPot - zStart + offsZ;
3738 const float p1 = deltaPot / (1 / offsZ - 1 / zMaxDeltaPotTmp);
3739 const float p2 = -p1 / zMaxDeltaPotTmp;
3740 const float absZShifted = zMaxDeltaPotTmp - (absZ - zStart);
3741 DataT pot = p2 + p1 / absZShifted;
3742 return pot;
3743 } else if (type == 0 || type == 4) {
3744 // linearly rising potential
3745 return static_cast<DataT>(deltaPot / (absZMaxDeltaPot - zStart) * (absZ - zStart) + offs);
3746 } else if (type == 2) {
3747 // flat
3748 return DataT(deltaPot);
3749 } else if (type == 3) {
3750 // linear falling
3751 return static_cast<DataT>(-deltaPot / (absZMaxDeltaPot - zStart) * (absZ - zStart) + deltaPot);
3752 } else {
3753 return DataT(0);
3754 }
3755 } else if (type == 4) {
3756 // flat no z dependence
3757 return DataT(offs);
3758 } else {
3759 return DataT(0);
3760 }
3761 };
3762 setPotentialBoundaryInnerRadius(chargeUpIFCLinear, side);
3763}
3764
3765template <typename DataT>
3766void SpaceCharge<DataT>::setIFCChargeUpFallingPot(const float deltaPot, const float zMaxDeltaPot, const int type, const float zEnd, const float offs, const Side side)
3767{
3768 std::function<DataT(DataT)> chargeUpIFCLinear = [zEnd, type, offs, zMax = getZMax(Side::A), deltaPot, zMaxDeltaPot](const DataT z) {
3769 const float absZ = std::abs(z);
3770 const float absZMaxDeltaPot = std::abs(zMaxDeltaPot);
3771
3772 bool check = (absZ >= absZMaxDeltaPot);
3773 if (type == 0 || type == 3) {
3774 check = (absZ >= absZMaxDeltaPot);
3775 }
3776
3777 if (check && (absZ <= zEnd)) {
3778 // 1/x dependency
3779 if (type == 1) {
3780 const float p1 = (deltaPot - offs) / (1 / zMaxDeltaPot - 1 / zEnd);
3781 const float p2 = offs - p1 / zEnd;
3782 DataT pot = p2 + p1 / absZ;
3783 return pot;
3784 } else if (type == 2) {
3785 // 1/x dependency steep fall off!
3786 const float offsZ = 1 + offs;
3787 const float zEndTmp = zEnd - zMaxDeltaPot + offsZ;
3788 const float p1 = deltaPot / (1 / offsZ - 1 / zEndTmp);
3789 const float p2 = -p1 / zEndTmp;
3790 const float absZShifted = absZ - zMaxDeltaPot + offsZ;
3791 DataT pot = p2 + p1 / absZShifted;
3792 return pot;
3793 } else if (type == 0 || type == 3) {
3794 // linearly falling potential
3795 const float zPos = absZ - zEnd;
3796 return static_cast<DataT>(deltaPot / (absZMaxDeltaPot - zEnd) * zPos + offs);
3797 } else {
3798 return DataT(0);
3799 }
3800 } else if (type == 3) {
3801 return DataT(offs);
3802 } else {
3803 return DataT(0);
3804 }
3805 };
3806 setPotentialBoundaryInnerRadius(chargeUpIFCLinear, side);
3807}
3808
3809template <typename DataT>
3810void SpaceCharge<DataT>::setGlobalCorrections(const std::function<void(int sector, DataT gx, DataT gy, DataT gz, DataT& gCx, DataT& gCy, DataT& gCz)>& gCorr, const Side side)
3811{
3812 setGlobalDistCorr(Type::Corrections, gCorr, side);
3813}
3814
3815template <typename DataT>
3816void SpaceCharge<DataT>::setGlobalDistortions(const std::function<void(int sector, DataT gx, DataT gy, DataT gz, DataT& gCx, DataT& gCy, DataT& gCz)>& gDist, const Side side)
3817{
3818 setGlobalDistCorr(Type::Distortions, gDist, side);
3819}
3820
3821template <typename DataT>
3822void SpaceCharge<DataT>::setGlobalDistCorr(const Type type, const std::function<void(int sector, DataT gx, DataT gy, DataT gz, DataT& gCx, DataT& gCy, DataT& gCz)>& gFunc, const Side side)
3823{
3824 if (type == Type::Distortions) {
3825 initContainer(mGlobalDistdR[side], true);
3826 initContainer(mGlobalDistdZ[side], true);
3827 initContainer(mGlobalDistdRPhi[side], true);
3828 } else {
3829 initContainer(mGlobalCorrdR[side], true);
3830 initContainer(mGlobalCorrdZ[side], true);
3831 initContainer(mGlobalCorrdRPhi[side], true);
3832 }
3833
3834#pragma omp parallel for num_threads(sNThreads)
3835 for (unsigned int iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
3836 const int sector = iPhi / (mParamGrid.NPhiVertices / SECTORSPERSIDE) + (side == Side::A ? 0 : SECTORSPERSIDE);
3837 DataT phi = getPhiVertex(iPhi, side);
3838 phi = o2::math_utils::detail::toPMPi(phi);
3839
3840 for (unsigned int iR = 0; iR < mParamGrid.NRVertices; ++iR) {
3841 const DataT radius = getRVertex(iR, side);
3842 const DataT x = getXFromPolar(radius, phi);
3843 const DataT y = getYFromPolar(radius, phi);
3844
3845 for (unsigned int iZ = 0; iZ < mParamGrid.NZVertices; ++iZ) {
3846 const DataT z = getZVertex(iZ, side);
3847
3848 // get corrected points
3849 DataT gCx = 0;
3850 DataT gCy = 0;
3851 DataT gCz = 0;
3852 gFunc(sector, x, y, z, gCx, gCy, gCz);
3853 const DataT gCxCorr = x + gCx;
3854 const DataT gCyCorr = y + gCy;
3855
3856 // get corrections
3857 const DataT corrR = getRadiusFromCartesian(gCxCorr, gCyCorr) - radius;
3858 DataT phiDiff = getPhiFromCartesian(gCxCorr, gCyCorr) - phi;
3859 phiDiff = o2::math_utils::detail::toPMPi(phiDiff);
3860 const DataT corrRPhi = phiDiff * radius;
3861
3862 // store corrections
3863 if (type == Type::Distortions) {
3864 mGlobalDistdR[side](iZ, iR, iPhi) = corrR;
3865 mGlobalDistdZ[side](iZ, iR, iPhi) = gCz;
3866 mGlobalDistdRPhi[side](iZ, iR, iPhi) = corrRPhi;
3867 } else {
3868 mGlobalCorrdR[side](iZ, iR, iPhi) = corrR;
3869 mGlobalCorrdZ[side](iZ, iR, iPhi) = gCz;
3870 mGlobalCorrdRPhi[side](iZ, iR, iPhi) = corrRPhi;
3871 }
3872 }
3873 }
3874 }
3875}
3876
3877template <typename DataT>
3878void SpaceCharge<DataT>::setROCMisalignmentShiftZ(const int sector, const int type, const float potential)
3879{
3880 setROCMisalignment(type, 2, sector, potential, potential);
3881}
3882
3883template <typename DataT>
3884void SpaceCharge<DataT>::setROCMisalignmentRotationAlongX(const int sector, const int type, const float potentialMin, const float potentialMax)
3885{
3886 setROCMisalignment(type, 0, sector, potentialMin, potentialMax);
3887}
3888
3889template <typename DataT>
3890void SpaceCharge<DataT>::setROCMisalignmentRotationAlongY(const int sector, const int type, const float potentialMin, const float potentialMax)
3891{
3892 setROCMisalignment(type, 1, sector, potentialMin, potentialMax);
3893}
3894
3895template <typename DataT>
3896void SpaceCharge<DataT>::setROCMisalignment(int stackType, int misalignmentType, int sector, const float potMin, const float potMax)
3897{
3898 initContainer(mPotential[Sector(sector).side()], true);
3899 const auto indPhiTopIROC = getPotentialBoundaryGEMFrameAlongPhiIndices(GEMstack::IROCgem, false, Side::A, false, true);
3900
3901 const auto rotationPoints = [](const int regStart, const int regEnd, int misalignmentType, const float potMax, const float potMin) {
3902 if (misalignmentType == 0) {
3903 const auto& mapper = o2::tpc::Mapper::instance();
3904 const float radStart = mapper.getPadRegionInfo(regStart).getRadiusFirstRow();
3905 const auto& padReg = mapper.getPadRegionInfo(regEnd);
3906 const float radEnd = padReg.getRadiusFirstRow() + padReg.getPadHeight() * padReg.getNumberOfPadRows() - radStart;
3907 const float rotationPoint = radStart + radEnd / 2;
3908 const float slope = (potMax - potMin) / radEnd;
3909 return std::pair<float, float>{rotationPoint, slope};
3910 } else if (misalignmentType == 1) {
3911 return std::pair<float, float>{0, (potMax - potMin) / 100};
3912 } else {
3913 return std::pair<float, float>{0, (potMax + potMin) / 2};
3914 }
3915 };
3916
3917 if (stackType == 0) {
3918 const auto deltaPotPar = rotationPoints(0, 3, misalignmentType, potMax, potMin);
3919 const auto indPhiBottomIROC = getPotentialBoundaryGEMFrameAlongPhiIndices(GEMstack::IROCgem, true, Side::A, false, true);
3920 fillROCMisalignment(indPhiTopIROC, indPhiBottomIROC, sector, misalignmentType, deltaPotPar);
3921 } else if (stackType == 1) {
3922 const auto deltaPotPar = rotationPoints(4, 9, misalignmentType, potMax, potMin);
3923 const auto indPhiTopOROC3 = getPotentialBoundaryGEMFrameAlongPhiIndices(GEMstack::OROC3gem, false, Side::A, false, true);
3924 fillROCMisalignment(indPhiTopOROC3, indPhiTopIROC, sector, misalignmentType, deltaPotPar);
3925 } else if (stackType == 2) {
3926 const auto deltaPotPar = rotationPoints(0, 9, misalignmentType, potMax, potMin);
3927 const auto indPhiBottomIROC = getPotentialBoundaryGEMFrameAlongPhiIndices(GEMstack::IROCgem, true, Side::A, false, true);
3928 const auto indPhiTopOROC3 = getPotentialBoundaryGEMFrameAlongPhiIndices(GEMstack::OROC3gem, false, Side::A, false, true);
3929 fillROCMisalignment(indPhiTopIROC, indPhiBottomIROC, sector, misalignmentType, deltaPotPar);
3930 fillROCMisalignment(indPhiTopOROC3, indPhiTopIROC, sector, misalignmentType, deltaPotPar);
3931 }
3932}
3933
3934template <typename DataT>
3935void SpaceCharge<DataT>::fillROCMisalignment(const std::vector<std::pair<size_t, float>>& indicesTop, const std::vector<std::pair<size_t, float>>& indicesBottom, int sector, int misalignmentType, const std::pair<float, float>& deltaPotPar)
3936{
3937 for (const auto& indexw : indicesTop) {
3938 const int index = indexw.first;
3939 const int iZ = DataContainer3D<float>::getIndexZ(index, getNZVertices(), getNRVertices(), getNPhiVertices());
3940 const int iRStart = DataContainer3D<float>::getIndexR(index, getNZVertices(), getNRVertices(), getNPhiVertices());
3941 const int iPhi = DataContainer3D<float>::getIndexPhi(index, getNZVertices(), getNRVertices(), getNPhiVertices());
3942
3943 const int sectorTmp = iPhi / (getNPhiVertices() / SECTORSPERSIDE) + ((sector >= SECTORSPERSIDE) ? SECTORSPERSIDE : 0);
3944 if ((sector != -1) && (sectorTmp != sector)) {
3945 continue;
3946 }
3947 const Sector sec(sectorTmp);
3948
3949 for (size_t iR = iRStart; iR > 0; --iR) {
3950 const size_t currInd = (iZ + getNZVertices() * (iR + iPhi * getNRVertices()));
3951 const bool foundVertexBottom = std::binary_search(indicesBottom.begin(), indicesBottom.end(), std::make_pair(currInd, 0.0f), [](const auto& a, const auto& b) { return (a.first < b.first); });
3952
3953 if (foundVertexBottom) {
3954 break;
3955 }
3956
3957 // get local coordinates
3958 const float rPos = getRVertex(iR, sec.side());
3959 const float phiPos = getPhiVertex(iPhi, sec.side());
3960 const float zPos = getZVertex(iZ, sec.side());
3961 const float x = getXFromPolar(rPos, phiPos);
3962 const float y = getYFromPolar(rPos, phiPos);
3963 const LocalPosition3D pos(x, y, zPos);
3964 const LocalPosition3D lPos = Mapper::GlobalToLocal(pos, sec);
3965 float deltaPot = 0;
3966 if (misalignmentType == 0) {
3967 deltaPot = (lPos.X() - deltaPotPar.first) * deltaPotPar.second;
3968 } else if (misalignmentType == 1) {
3969 deltaPot = lPos.Y() * deltaPotPar.second;
3970 } else {
3971 deltaPot = deltaPotPar.second;
3972 }
3973 mPotential[sec.side()](iZ, iR, iPhi) += deltaPot;
3974 }
3975 }
3976}
3977
3978template <typename DataT>
3980{
3981 mGlobalCorrdR[side] -= otherSC.mGlobalCorrdR[side];
3982 mGlobalCorrdZ[side] -= otherSC.mGlobalCorrdZ[side];
3983 mGlobalCorrdRPhi[side] -= otherSC.mGlobalCorrdRPhi[side];
3984}
3985
3986template <typename DataT>
3988{
3989 mGlobalDistdR[side] -= otherSC.mGlobalDistdR[side];
3990 mGlobalDistdZ[side] -= otherSC.mGlobalDistdZ[side];
3991 mGlobalDistdRPhi[side] -= otherSC.mGlobalDistdRPhi[side];
3992}
3993
3994template <typename DataT>
3996{
3997 mGlobalCorrdR[side] *= val;
3998 mGlobalCorrdZ[side] *= val;
3999 mGlobalCorrdRPhi[side] *= val;
4000}
4001
4002template <typename DataT>
4004{
4005 initContainer(mDensity[side], true);
4006 const int verticesPerSector = mParamGrid.NPhiVertices / SECTORSPERSIDE;
4007 for (unsigned int iR = 0; iR < mParamGrid.NRVertices; ++iR) {
4008 for (unsigned int iZ = 0; iZ < mParamGrid.NZVertices; ++iZ) {
4009 for (unsigned int iPhi = 0; iPhi <= (verticesPerSector / 2); ++iPhi) {
4010 float meanDensity = 0;
4011 for (int iter = 0; iter < 2; ++iter) {
4012 for (unsigned int sec = 0; sec < SECTORSPERSIDE; ++sec) {
4013 const int iPhiTmpA = iPhi + sec * verticesPerSector;
4014 const int iPhiTmpB = ((sec + 1) * verticesPerSector - iPhi) % mParamGrid.NPhiVertices;
4015 if (iter == 0) {
4016 meanDensity += mDensity[side](iZ, iR, iPhiTmpA);
4017 meanDensity += mDensity[side](iZ, iR, iPhiTmpB);
4018 } else {
4019 const float densMean = meanDensity / (2 * SECTORSPERSIDE);
4020 mDensity[side](iZ, iR, iPhiTmpA) = densMean;
4021 mDensity[side](iZ, iR, iPhiTmpB) = densMean;
4022 }
4023 }
4024 }
4025 }
4026 }
4027 }
4028}
4029
4030template <typename DataT>
4031void SpaceCharge<DataT>::scaleChargeDensitySector(const float scalingFactor, const Sector sector)
4032{
4033 const Side side = sector.side();
4034 initContainer(mDensity[side], true);
4035 const int verticesPerSector = mParamGrid.NPhiVertices / SECTORSPERSIDE;
4036 const int sectorInSide = sector % SECTORSPERSIDE;
4037 const int iPhiFirst = sectorInSide * verticesPerSector;
4038 const int iPhiLast = iPhiFirst + verticesPerSector;
4039 for (unsigned int iR = 0; iR < mParamGrid.NRVertices; ++iR) {
4040 for (unsigned int iZ = 0; iZ < mParamGrid.NZVertices; ++iZ) {
4041 for (unsigned int iPhi = iPhiFirst; iPhi < iPhiLast; ++iPhi) {
4042 mDensity[side](iZ, iR, iPhi) *= scalingFactor;
4043 }
4044 }
4045 }
4046}
4047
4048template <typename DataT>
4049void SpaceCharge<DataT>::scaleChargeDensityStack(const float scalingFactor, const Sector sector, const GEMstack stack)
4050{
4051 const Side side = sector.side();
4052 initContainer(mDensity[side], true);
4053 const int verticesPerSector = mParamGrid.NPhiVertices / SECTORSPERSIDE;
4054 const int sectorInSide = sector % SECTORSPERSIDE;
4055 const int iPhiFirst = sectorInSide * verticesPerSector;
4056 const int iPhiLast = iPhiFirst + verticesPerSector;
4057 for (unsigned int iR = 0; iR < mParamGrid.NRVertices; ++iR) {
4058 const DataT radius = getRVertex(iR, side);
4059 for (unsigned int iPhi = iPhiFirst; iPhi < iPhiLast; ++iPhi) {
4060 const DataT phi = getPhiVertex(iR, side);
4061 const GlobalPosition3D pos(getXFromPolar(radius, phi), getYFromPolar(radius, phi), ((side == Side::A) ? 10 : -10));
4062 const auto& mapper = o2::tpc::Mapper::instance();
4063 const o2::tpc::DigitPos digiPadPos = mapper.findDigitPosFromGlobalPosition(pos);
4064 if (digiPadPos.isValid() && digiPadPos.getCRU().gemStack() == stack) {
4065 for (unsigned int iZ = 0; iZ < mParamGrid.NZVertices; ++iZ) {
4066 mDensity[side](iZ, iR, iPhi) *= scalingFactor;
4067 }
4068 }
4069 }
4070 }
4071}
4072
4073template <typename DataT>
4075{
4076 mGrid3D[Side::A] = RegularGrid(GridProp::ZMIN, GridProp::RMIN, GridProp::PHIMIN, getSign(Side::A) * GridProp::getGridSpacingZ(mParamGrid.NZVertices), GridProp::getGridSpacingR(mParamGrid.NRVertices), GridProp::getGridSpacingPhi(mParamGrid.NPhiVertices), mParamGrid);
4077 mGrid3D[Side::C] = RegularGrid(GridProp::ZMIN, GridProp::RMIN, GridProp::PHIMIN, getSign(Side::C) * GridProp::getGridSpacingZ(mParamGrid.NZVertices), GridProp::getGridSpacingR(mParamGrid.NRVertices), GridProp::getGridSpacingPhi(mParamGrid.NPhiVertices), mParamGrid);
4078}
4079
4080template <typename DataT>
4081float SpaceCharge<DataT>::getDCAr(float tgl, const int nPoints, const float phi, float rStart, o2::utils::TreeStreamRedirector* pcstream) const
4082{
4083 const float rmin = getRMin(o2::tpc::Side::A);
4084 std::vector<float> dRphi;
4085 std::vector<float> r;
4086 dRphi.reserve(nPoints);
4087 r.reserve(nPoints);
4088 for (int i = 0; i < nPoints; ++i) {
4089 float radius = (rStart > 0) ? (rStart + i) : (rmin + i);
4090 float z = tgl * radius;
4091 DataT distZ = 0;
4092 DataT distR = 0;
4093 DataT distRPhi = 0;
4094 getDistortionsCyl(z, radius, phi, o2::tpc::Side::A, distZ, distR, distRPhi);
4095 dRphi.emplace_back(distRPhi);
4096 r.emplace_back(radius);
4097 }
4098
4099 TF1 fPol("pol2", "pol2", rmin, r.back());
4100 fPol.SetParameter(0, 0);
4101 fPol.SetParameter(1, 0);
4102 fPol.SetParameter(2, 0);
4103 TGraph gr(r.size(), r.data(), dRphi.data());
4104 gr.Fit(&fPol, "QNRC");
4105 float dca = fPol.Eval(0);
4106 if (pcstream) {
4107 std::vector<double> params{fPol.GetParameter(0), fPol.GetParameter(1), fPol.GetParameter(2)};
4108 std::vector<float> rInterpol;
4109 std::vector<float> dRPhiInterpol;
4110 std::vector<float> distanceInterpol;
4111
4112 for (int i = 0; i < 500; ++i) {
4113 float radius = rmin + float(i) / 10;
4114 rInterpol.emplace_back(radius);
4115 dRPhiInterpol.emplace_back(fPol.Eval(radius));
4116 distanceInterpol.emplace_back(std::sqrt(rInterpol.back() * rInterpol.back() + dRPhiInterpol.back() * dRPhiInterpol.back()));
4117 }
4118
4119 for (int i = -200; i < 200; ++i) {
4120 float radius = float(i) / 10;
4121 rInterpol.emplace_back(radius);
4122 dRPhiInterpol.emplace_back(fPol.Eval(radius));
4123 distanceInterpol.emplace_back(std::sqrt(rInterpol.back() * rInterpol.back() + dRPhiInterpol.back() * dRPhiInterpol.back()));
4124 }
4125 (*pcstream) << "tree"
4126 << "r=" << r
4127 << "dRphi=" << dRphi
4128 << "tgl=" << tgl
4129 << "phi=" << phi
4130 << "dca=" << dca
4131 << "rInterpol=" << rInterpol
4132 << "dRPhiInterpol=" << dRPhiInterpol
4133 << "distanceInterpol=" << distanceInterpol
4134 << "param=" << params
4135 << "\n";
4136 }
4137 return dca;
4138}
4139
4140template <typename DataT>
4141void SpaceCharge<DataT>::setPotential(int iz, int ir, int iphi, Side side, float val)
4142{
4143 initContainer(mPotential[side], true);
4144 mPotential[side](iz, ir, iphi) = val;
4145}
4146
4147template <typename DataT>
4148void SpaceCharge<DataT>::downSampleObject(const int nZNew, const int nRNew, const int nPhiNew)
4149{
4150 o2::tpc::SpaceCharge<DataT> scNew(getBField(), nZNew, nRNew, nPhiNew);
4151 for (int iside = 0; iside < 2; ++iside) {
4152 const o2::tpc::Side side = (iside == 0) ? o2::tpc::Side::A : o2::tpc::Side::C;
4153 const std::vector<std::reference_wrapper<const DataContainer>> dataRef{mLocalDistdR[iside], mLocalDistdZ[iside], mLocalDistdRPhi[iside], mLocalVecDistdR[iside], mLocalVecDistdZ[iside], mLocalVecDistdRPhi[iside], mLocalCorrdR[iside], mLocalCorrdZ[iside], mLocalCorrdRPhi[iside], mGlobalDistdR[iside], mGlobalDistdZ[iside], mGlobalDistdRPhi[iside], mGlobalCorrdR[iside], mGlobalCorrdZ[iside], mGlobalCorrdRPhi[iside], mDensity[iside], mPotential[iside], mElectricFieldEr[iside], mElectricFieldEz[iside], mElectricFieldEphi[iside]};
4154 const std::vector<std::reference_wrapper<DataContainer>> dataNew{scNew.mLocalDistdR[iside], scNew.mLocalDistdZ[iside], scNew.mLocalDistdRPhi[iside], scNew.mLocalVecDistdR[iside], scNew.mLocalVecDistdZ[iside], scNew.mLocalVecDistdRPhi[iside], scNew.mLocalCorrdR[iside], scNew.mLocalCorrdZ[iside], scNew.mLocalCorrdRPhi[iside], scNew.mGlobalDistdR[iside], scNew.mGlobalDistdZ[iside], scNew.mGlobalDistdRPhi[iside], scNew.mGlobalCorrdR[iside], scNew.mGlobalCorrdZ[iside], scNew.mGlobalCorrdRPhi[iside], scNew.mDensity[iside], scNew.mPotential[iside], scNew.mElectricFieldEr[iside], scNew.mElectricFieldEz[iside], scNew.mElectricFieldEphi[iside]};
4155 for (int i = 0; i < dataRef.size(); ++i) {
4156 const auto& objRef = dataRef[i].get();
4157 if (objRef.getNDataPoints()) {
4158 auto& objNew = dataNew[i].get();
4159 scNew.initContainer(objNew, true);
4160 objNew = objRef.convert(scNew.mGrid3D[iside], mGrid3D[iside], sNThreads);
4161 }
4162 }
4163 }
4164 *this = std::move(scNew);
4165}
4166
4167using DataTD = double;
4168template class o2::tpc::SpaceCharge<DataTD>;
4169
4174
4179template void O2TPCSpaceCharge3DCalcD::calcGlobalCorrections(const NumFieldsD&, const int);
4180template void O2TPCSpaceCharge3DCalcD::calcGlobalCorrections(const AnaFieldsD&, const int);
4182template void O2TPCSpaceCharge3DCalcD::calcGlobalDistortions(const NumFieldsD&, const int maxIterations);
4183template void O2TPCSpaceCharge3DCalcD::calcGlobalDistortions(const AnaFieldsD&, const int maxIterations);
4184template void O2TPCSpaceCharge3DCalcD::calcGlobalDistortions(const DistCorrInterpD&, const int maxIterations);
4185template void O2TPCSpaceCharge3DCalcD::setGlobalCorrectionsFromFile<double>(TFile&, const Side);
4186template void O2TPCSpaceCharge3DCalcD::setGlobalCorrectionsFromFile<float>(TFile&, const Side);
4187template void O2TPCSpaceCharge3DCalcD::setGlobalDistortionsFromFile<double>(TFile&, const Side);
4188template void O2TPCSpaceCharge3DCalcD::setGlobalDistortionsFromFile<float>(TFile&, const Side);
4189
4190using DataTF = float;
4191template class o2::tpc::SpaceCharge<DataTF>;
4192
4197
4202template void O2TPCSpaceCharge3DCalcF::calcGlobalCorrections(const NumFieldsF&, const int);
4203template void O2TPCSpaceCharge3DCalcF::calcGlobalCorrections(const AnaFieldsF&, const int);
4205template void O2TPCSpaceCharge3DCalcF::calcGlobalDistortions(const NumFieldsF&, const int maxIterations);
4206template void O2TPCSpaceCharge3DCalcF::calcGlobalDistortions(const AnaFieldsF&, const int maxIterations);
4207template void O2TPCSpaceCharge3DCalcF::calcGlobalDistortions(const DistCorrInterpF&, const int maxIterations);
4208template void O2TPCSpaceCharge3DCalcF::setGlobalCorrectionsFromFile<double>(TFile&, const Side);
4209template void O2TPCSpaceCharge3DCalcF::setGlobalCorrectionsFromFile<float>(TFile&, const Side);
4210template void O2TPCSpaceCharge3DCalcF::setGlobalDistortionsFromFile<double>(TFile&, const Side);
4211template void O2TPCSpaceCharge3DCalcF::setGlobalDistortionsFromFile<float>(TFile&, const Side);
void dumpToFile(std::string fileName, const CathodeSegmentation &seg, const std::vector< Point > &points)
General auxilliary methods.
const auto bins
Definition PID.cxx:49
int16_t charge
Definition RawEventData.h:5
int16_t time
Definition RawEventData.h:4
int32_t i
#define GPUCA_DEBUG_STREAMER_CHECK(...)
float float float & zMax
float float & zMin
Header of the General Run Parameters object for B field values.
constexpr int p2()
constexpr int p1()
constexpr to accelerate the coordinates changing
Header to collect LHC related constants.
Definition of the MagF class.
Definition of the parameter class for the detector electronics.
Definition of the parameter class for the detector gas.
This class provides implementation of Poisson equation solver by MultiGrid Method Original version of...
uint16_t pos
Definition RawData.h:3
uint32_t side
Definition RawData.h:0
uint16_t slope
Definition RawData.h:1
uint32_t stack
Definition RawData.h:1
templateClassImp(o2::tpc::SpaceCharge)
float DataTF
double DataTD
This class contains the algorithms for calculation the distortions and corrections.
uint32_t cols
Double_t solenoidField() const
void setDipoleCurrent(o2::units::Current_t v)
Definition GRPMagField.h:52
void setL3Current(o2::units::Current_t v)
Definition GRPMagField.h:51
struct for containing simple analytical distortions (as function of local coordinates) and the result...
DataT evalFieldPhi(DataT z, DataT r, DataT phi) const
DataT evalDensity(DataT z, DataT r, DataT phi) const
DataT evalFieldR(DataT z, DataT r, DataT phi) const
DataT evalPotential(DataT z, DataT r, DataT phi) const
o2::tpc::Side getSide() const
DataT evalFieldZ(DataT z, DataT r, DataT phi) const
GEMstack gemStack() const
Definition CRU.h:82
const CRU & getCRU() const
Definition DigitPos.h:30
bool isValid() const
Definition DigitPos.h:38
DataT evaldZ(const DataT z, const DataT r, const DataT phi) const
DataT evaldRPhi(const DataT z, const DataT r, const DataT phi) const
DataT evaldR(const DataT z, const DataT r, const DataT phi) const
static constexpr unsigned int ROWOFFSET[NREGIONS]
offset to calculate local row from global row
Definition Mapper.h:533
static GlobalPadNumber getGlobalPadNumber(const unsigned int lrow, const unsigned int pad, const unsigned int region)
Definition Mapper.h:64
const DigitPos findDigitPosFromGlobalPosition(const GlobalPosition3D &pos) const
Definition Mapper.h:716
static const std::vector< unsigned int > PADSPERROW[NREGIONS]
number of pads per row in region
Definition Mapper.h:567
static constexpr float INVPADAREA[NREGIONS]
inverse size of the pad area padwidth*padLength
Definition Mapper.h:536
static Mapper & instance(const std::string mappingDir="")
Definition Mapper.h:44
static LocalPosition3D GlobalToLocal(const GlobalPosition3D &pos, const double alpha)
Definition Mapper.h:468
static constexpr unsigned int ROWSPERREGION[NREGIONS]
number of pad rows for region
Definition Mapper.h:532
static constexpr unsigned int NSECTORS
total number of sectors in the TPC
Definition Mapper.h:526
const PadRegionInfo & getPadRegionInfo(const unsigned char region) const
Definition Mapper.h:385
static constexpr unsigned int NREGIONS
total number of regions in one sector
Definition Mapper.h:527
static constexpr unsigned short getPadsInSector()
Definition Mapper.h:414
const PadCentre & padCentre(GlobalPadNumber padNumber) const
Definition Mapper.h:51
static GlobalPosition3D LocalToGlobal(const LocalPosition3D &pos, const double alpha)
Definition Mapper.h:461
float getRadiusFirstRow() const
unsigned char getPadsInRowRegion(const int row) const
static void setConvergenceError(const DataT error)
void poissonSolver3D(DataContainer &matricesV, const DataContainer &matricesCharge, const int symmetry)
Side side() const
Definition Sector.h:96
void setOmegaTauT1T2(const DataT omegaTau=0.32f, const DataT t1=1, const DataT t2=1)
void getDistortionsCyl(const DataT z, const DataT r, const DataT phi, const Side side, DataT &distZ, DataT &distR, DataT &distRPhi) const
void addChargeDensity(const SpaceCharge< DataT > &otherSC)
void dumpToTree(const char *outFileName, const Side side, const int nZPoints=50, const int nRPoints=150, const int nPhiPoints=180, const bool randomize=false) const
void correctElectron(GlobalPosition3D &point)
void initBField(const int field)
DataT regulateR(const DataT posR, const Side side) const
set r coordinate between 'RMIN - 4 * GRIDSPACINGR' and 'RMAX + 2 * GRIDSPACINGR'. the r coordinate is...
size_t getPhiBinsGapFrame(const Side side) const
void setROCMisalignmentRotationAlongX(const int sector, const int type, const float potentialMin, const float potentialMax)
int dumpGlobalDistortions(std::string_view file, const Side side, std::string_view option) const
void addGlobalCorrections(const SpaceCharge< DataT > &otherSC, const Side side)
add global corrections from other space charge object
void setPotentialBoundaryInnerRadius(const std::function< DataT(DataT)> &potentialFunc, const Side side)
void setLocalCorrectionsFromFile(std::string_view file, const Side side)
void setPotentialBoundaryGEMFrameAlongR(const std::function< DataT(DataT)> &potentialFunc, const Side side)
void calcGlobalDistWithGlobalCorrIterative(const DistCorrInterpolator< DataT > &globCorr, const int maxIter=100, const DataT approachZ=1, const DataT approachR=1, const DataT approachPhi=1, const DataT diffCorr=50e-6, const SpaceCharge< DataT > *scSCale=nullptr, float scale=0)
int dumpLocalCorrections(std::string_view file, const Side side, std::string_view option) const
void setGlobalDistortionsFromFile(std::string_view file, const Side side)
bool checkGridFromFile(std::string_view file, std::string_view tree)
void setSimNSector(const int nSectors)
simulate N sectors
void setGlobalCorrectionsFromFile(std::string_view file, const Side side)
DataT getPotentialCyl(const DataT z, const DataT r, const DataT phi, const Side side) const
void setROCMisalignmentRotationAlongY(const int sector, const int type, const float potentialMin, const float potentialMax)
int dumpDensity(std::string_view file, const Side side, std::string_view option) const
void setIFCChargeUpRisingPot(const float deltaPot, const float zMaxDeltaPot, const int type, const float zStart, const float offs, const Side side)
void getLocalDistortionVectorCyl(const DataT z, const DataT r, const DataT phi, const Side side, DataT &lvecdistZ, DataT &lvecdistR, DataT &lvecdistRPhi) const
int dumpPotential(std::string_view file, const Side side, std::string_view option) const
unsigned short getNPhiVertices() const
void setDefaultStaticDistortionsGEMFrameChargeUp(const Side side, const DataT deltaPotential=1000)
void setAnalyticalCorrectionsDistortionsFromFile(std::string_view inpf)
void fillChargeFromCalDet(const std::vector< CalDet< float > > &calCharge3D)
void setPotentialBoundaryFromFormula(const AnalyticalFields< DataT > &formulaStruct)
void calcGlobalCorrWithGlobalDistIterativeLinearCartesian(const Side side, const SpaceCharge< DataT > *scSCale=nullptr, float scale=0, const int maxIter=100, const DataT approachX=1, const DataT approachY=1, const DataT approachZ=1, const DataT diffCorr=50e-6)
void calcGlobalCorrWithGlobalDistIterative(const Side side, const SpaceCharge< DataT > *scSCale=nullptr, float scale=0, const int maxIter=100, const DataT approachZ=1, const DataT approachR=1, const DataT approachPhi=1, const DataT diffCorr=50e-6)
void calcLocalDistortionsCorrections(const Type type, const ElectricFields &formulaStruct)
int dumpGlobalCorrections(std::string_view file, const Side side, std::string_view option) const
DistCorrInterpolator< DataT > getGlobalCorrInterpolator(const Side side) const
DistCorrInterpolator< DataT > getLocalCorrInterpolator(const Side side) const
void setEFieldFromFormula(const AnalyticalFields< DataT > &formulaStruct)
void subtractGlobalDistortions(const SpaceCharge< DataT > &otherSC, const Side side)
int dumpLocalDistCorrVectors(std::string_view file, const Side side, std::string_view option) const
void addBoundaryPotential(const SpaceCharge< DataT > &other, const Side side, const float scaling=1)
void getCorrections(const DataT x, const DataT y, const DataT z, const Side side, DataT &corrX, DataT &corrY, DataT &corrZ) const
static void normalizeHistoQVEps0(TH3 &histoIonsPhiRZ)
void getDistortions(const DataT x, const DataT y, const DataT z, const Side side, DataT &distX, DataT &distY, DataT &distZ) const
void calcLocalDistortionsCorrectionsRK4(const Type type, const Side side)
void mirrorPotential(const Side sideRef, const Side sideMirrored)
static int getOMPMaxThreads()
int dumpAnalyticalCorrectionsDistortions(TFile &outf) const
static void unsetSimNSector()
unsetting simulation of one sector
DistCorrInterpolator< DataT > getLocalDistInterpolator(const Side side) const
void getLocalCorrectionsCyl(const DataT z, const DataT r, const DataT phi, const Side side, DataT &lcorrZ, DataT &lcorrR, DataT &lcorrRPhi) const
void fillChargeFromIDCs(std::vector< CalDet< float > > &idcZero, const CalDet< float > &mapIBF, const float ionDriftTimeMS=200, const bool normToPadArea=true)
void scaleChargeDensityStack(const float scalingFactor, const Sector sector, const GEMstack stack)
scaling the space-charge density for given stack
void calcGlobalDistortions(const Fields &formulaStruct, const int maxIterations=3 *sSteps *129)
void dumpMetaData(std::string_view file, std::string_view option, const bool overwriteExisting=false) const
static void convertIDCsToCharge(std::vector< CalDet< float > > &idcZero, const CalDet< float > &mapIBF, const float ionDriftTimeMS=200, const bool normToPadArea=true)
const auto & getDistortionsCorrectionsAnalytical() const &
NumericalFields< DataT > getElectricFieldsInterpolator(const Side side) const
void subtractGlobalCorrections(const SpaceCharge< DataT > &otherSC, const Side side)
void getCorrectionsCyl(const DataT z, const DataT r, const DataT phi, const Side side, DataT &corrZ, DataT &corrR, DataT &corrRPhi) const
void setDensityFromFile(std::string_view file, const Side side)
void getLocalDistortionsCyl(const DataT z, const DataT r, const DataT phi, const Side side, DataT &ldistZ, DataT &ldistR, DataT &ldistRPhi) const
void dumpToFile(std::string_view file, const Side side, std::string_view option) const
void fillChargeDensityFromCalDet(const std::vector< CalDet< float > > &calSCDensity3D)
void setGlobalDistortions(const std::function< void(int sector, DataT gx, DataT gy, DataT gz, DataT &gCx, DataT &gCy, DataT &gCz)> &gDist, const Side side)
void scaleCorrections(const float scaleFac, const Side side)
int dumpLocalDistortions(std::string_view file, const Side side, std::string_view option) const
unsigned short getNRVertices() const
void setDistortionLookupTables(const DataContainer &distdZ, const DataContainer &distdR, const DataContainer &distdRPhi, const Side side)
void setPotentialBoundaryOuterRadius(const std::function< DataT(DataT)> &potentialFunc, const Side side)
void setIFCChargeUpFallingPot(const float deltaPot, const float zMaxDeltaPot, const int type, const float zEnd, const float offs, const Side side)
void calcGlobalDistWithGlobalCorrIterativeLinearCartesian(const Side side, const SpaceCharge< DataT > *scSCale=nullptr, float scale=0, const int maxIter=100, const DataT approachX=1, const DataT approachY=1, const DataT approachZ=1, const DataT diffCorr=50e-6)
static void makeElectronDriftPathGif(const char *inpFile, TH2F &hBorder, const int type=0, const int gifSpeed=2, const int maxsamplingpoints=100, const char *outName="electron_drift_path")
void setElectricFieldsFromFile(std::string_view file, const Side side)
void calcEField(const Side side)
void setPotentialFromFile(std::string_view file, const Side side)
void fillChargeDensityFromHisto(const TH3 &hisSCDensity3D)
void getLocalCorrectionVectorCyl(const DataT z, const DataT r, const DataT phi, const Side side, DataT &lveccorrZ, DataT &lveccorrR, DataT &lveccorrRPhi) const
void setPotential(int iz, int ir, int iphi, Side side, float val)
setting the potential directly for given vertex
void setFromFile(std::string_view file, const Side side)
SpaceCharge()
constructor for empty object. Can be used when buffers are loaded from file
void setChargeDensityFromFormula(const AnalyticalFields< DataT > &formulaStruct)
void averageDensityPerSector(const Side side)
average the sc density across all sectors per side
DataT getDensityCyl(const DataT z, const DataT r, const DataT phi, const Side side) const
void getElectricFieldsCyl(const DataT z, const DataT r, const DataT phi, const Side side, DataT &eZ, DataT &eR, DataT &ePhi) const
float getDCAr(float tgl, const int nPoints, const float phi, float rStart=-1, o2::utils::TreeStreamRedirector *pcstream=nullptr) const
DistCorrInterpolator< DataT > getGlobalDistInterpolator(const Side side) const
void setROCMisalignmentShiftZ(const int sector, const int type, const float potential)
void calculateDistortionsCorrections(const o2::tpc::Side side, const bool calcVectors=false)
void calcGlobalCorrections(const Fields &formulaStruct, const int type=3)
void setGlobalCorrections(const std::function< void(int sector, DataT gx, DataT gy, DataT gz, DataT &gCx, DataT &gCy, DataT &gCz)> &gCorr, const Side side)
void calcLocalDistortionCorrectionVector(const ElectricFields &formulaStruct)
void setPotentialFromFormula(const AnalyticalFields< DataT > &formulaStruct)
void setLocalDistCorrVectorsFromFile(std::string_view file, const Side side)
void distortElectron(GlobalPosition3D &point, const SpaceCharge< DataT > *scSCale=nullptr, float scale=0) const
unsigned short getNZVertices() const
void readMetaData(std::string_view file)
void resetBoundaryPotentialToZeroInRangeZ(float zMin, float zMax, const Side side)
setting the boundary potential to 0 for z<zMin and z>zMax
void downSampleObject(const int nZNew, const int nRNew, const int nPhiNew)
int dumpElectricFields(std::string_view file, const Side side, std::string_view option) const
void setLocalDistortionsFromFile(std::string_view file, const Side side)
void scaleChargeDensitySector(const float scalingFactor, const Sector sector)
std::vector< std::pair< std::vector< o2::math_utils::Point3D< float > >, std::array< DataT, 3 > > > calculateElectronDriftPath(const std::vector< GlobalPosition3D > &elePos, const int nSamplingPoints, const std::string_view outFile="electron_tracks.root") const
void fillChargeDensityFromFile(TFile &fInp, const char *name)
void poissonSolver(const Side side, const DataT stoppingConvergence=1e-6, const int symmetry=0)
GLint GLenum GLint x
Definition glcorearb.h:403
GLuint entry
Definition glcorearb.h:5735
GLsizeiptr size
Definition glcorearb.h:659
GLuint index
Definition glcorearb.h:781
GLuint const GLchar * name
Definition glcorearb.h:781
GLdouble f
Definition glcorearb.h:310
GLuint GLuint GLfloat weight
Definition glcorearb.h:5477
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLint y
Definition glcorearb.h:270
GLint GLint GLsizei GLint GLenum GLenum type
Definition glcorearb.h:275
GLenum const GLfloat * params
Definition glcorearb.h:272
GLboolean * data
Definition glcorearb.h:298
GLsizei GLenum const void * indices
Definition glcorearb.h:400
typedef void(APIENTRYP PFNGLCULLFACEPROC)(GLenum mode)
GLint GLint bottom
Definition glcorearb.h:1979
GLuint GLfloat * val
Definition glcorearb.h:1582
GLboolean r
Definition glcorearb.h:1233
GLuint start
Definition glcorearb.h:469
GLboolean GLboolean GLboolean GLboolean a
Definition glcorearb.h:1233
GLdouble GLdouble GLdouble z
Definition glcorearb.h:843
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat t1
Definition glcorearb.h:5034
constexpr int LHCMaxBunches
constexpr double LHCOrbitMUS
void check(const std::vector< std::string > &arguments, const std::vector< ConfigParamSpec > &workflowOptions, const std::vector< DeviceSpec > &deviceSpecs, CheckMatrix &matrix)
void bringTo02PiGen(T &phi)
void bringTo02PiGen(float &phi)
Definition Utils.h:80
constexpr int LHCBCPERTIMEBIN
Definition Constants.h:38
Global TPC definitions and constants.
Definition SimTraits.h:168
GEMstack
TPC GEM stack types.
Definition Defs.h:53
@ IROCgem
Definition Defs.h:53
@ OROC1gem
Definition Defs.h:54
@ OROC3gem
Definition Defs.h:56
@ OROC2gem
Definition Defs.h:55
DataT getZVertex(const size_t indexZ, const o2::tpc::RegularGrid3D< DataT > &grid)
math_utils::Point2D< float > LocalPosition2D
Definition Defs.h:107
constexpr double PI
Definition Defs.h:43
DataT getRVertex(const size_t indexR, const o2::tpc::RegularGrid3D< DataT > &grid)
constexpr double TWOPI
Definition Defs.h:44
constexpr double SECPHIWIDTH
Definition Defs.h:45
constexpr unsigned char SECTORSPERSIDE
Definition Defs.h:40
DataT getPhiVertex(const size_t indexPhi, const o2::tpc::RegularGrid3D< DataT > &grid)
math_utils::Point3D< float > GlobalPosition3D
Definition Defs.h:108
constexpr unsigned char SIDES
Definition Defs.h:41
Side
TPC readout sidE.
Definition Defs.h:35
@ A
Definition Defs.h:35
@ C
Definition Defs.h:36
DataT sum(const Vector< DataT, N > &a)
Definition Vector.h:107
@ streamDistortionsSC
stream distortions applied in the TPC space-charge class (used for example in the tpc digitizer)
static size_t getIndexR(size_t index, const int nz, const int nr, const int nphi)
static size_t getIndexZ(size_t index, const int nz, const int nr, const int nphi)
static size_t getIndexPhi(size_t index, const int nz, const int nr, const int nphi)
static int normalizeGridToNSector
the grid in phi direction is squashed from 2 Pi to (2 Pi / SECTORSPERSIDE). This can used to get the ...
float ADCdynamicRange
Dynamic range of the ADC [mV].
float ADCsaturation
ADC saturation [ADC counts].
float ElectronCharge
Electron charge [C].
float ChipGain
Gain of the SAMPA [mV/fC] - may be either 20 or 30.
helper struct to store and set RegularGrid3D
static TH3F convertCalDetToTH3(const std::vector< CalDet< DataT > > &calDet, const bool norm=true, const int nRBins=150, const float rMin=83.5, const float rMax=254.5, const int nPhiBins=720, const float zMax=1)
VectorOfTObjectPtrs other
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"
o2::InteractionRecord ir(0, 0)
std::unique_ptr< TTree > tree((TTree *) flIn.Get(std::string(o2::base::NameConf::CTFTREENAME).c_str()))