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