Project
Loading...
Searching...
No Matches
SpaceCharge.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
17
19#include "fmt/core.h"
20#include "Framework/Logger.h"
22#include "TGeoGlobalMagField.h"
25#include "Field/MagneticField.h"
27#include "TPCBase/CalDet.h"
29#include "MathUtils/Utils.h"
31#include "GPUDebugStreamer.h"
34
35#include <numeric>
36#include <chrono>
37#include "TF1.h"
38#include "TH3.h"
39#include "TH2F.h"
40#include "TGraph.h"
41#include "TCanvas.h"
42#include "TROOT.h"
43#include "TStopwatch.h"
44#include "THnSparse.h"
45#include "TRandom.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;
207 }
208 return posR;
209}
211template <typename DataT>
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);
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{
250 return 0;
251 }
252
253 const auto& regInf = Mapper::instance().getPadRegionInfo(0);
254 const float localYEdgeIROC = regInf.getPadsInRowRegion(0) / 2 * regInf.getPadWidth();
255 const auto globalPosGap = Mapper::LocalToGlobal(LocalPosition2D(regInf.getRadiusFirstRow(), -(localYEdgeIROC + GEMFrameParameters<DataT>::WIDTHFRAME)), Sector(0));
256 const auto phiGap = std::atan(globalPosGap.Y() / globalPosGap.X());
257
258 auto nBinsPhiGap = getNearestPhiVertex(phiGap, side);
259 if (nBinsPhiGap == 0) {
260 nBinsPhiGap = 1;
261 }
262
263 return nBinsPhiGap;
264}
265
266template <typename DataT>
267void SpaceCharge<DataT>::setPotentialBoundaryGEMFrameAlongR(const std::function<DataT(DataT)>& potentialFunc, const Side side)
268{
269 initContainer(mPotential[side], true);
270 const auto indices = getPotentialBoundaryGEMFrameAlongRIndices(side);
271 setBoundaryFromIndices(potentialFunc, indices, side);
272}
273
274template <typename DataT>
275std::vector<std::pair<size_t, float>> SpaceCharge<DataT>::getPotentialBoundaryGEMFrameAlongRIndices(const Side side) const
277 const auto radiusStart = std::sqrt(std::pow(GEMFrameParameters<DataT>::LENGTHFRAMEIROCBOTTOM / 2, 2) + std::pow(GEMFrameParameters<DataT>::POSBOTTOM[0], 2));
278 const auto rStart = getNearestRVertex(radiusStart, side);
279
280 const auto radiusEnd = std::sqrt(std::pow(GEMFrameParameters<DataT>::LENGTHFRAMEOROC3TOP / 2, 2) + std::pow(GEMFrameParameters<DataT>::POSTOP[3], 2));
281 const auto rEnd = getNearestRVertex(radiusEnd, side); // mParamGrid.NRVertices - 1
282
283 const int verticesPerSector = mParamGrid.NPhiVertices / MGParameters::normalizeGridToNSector;
284
285 const auto& regInf = Mapper::instance().getPadRegionInfo(0);
286 const float localYEdgeIROC = regInf.getPadsInRowRegion(0) / 2 * regInf.getPadWidth();
287 const auto globalPosEdgeIROC = Mapper::LocalToGlobal(LocalPosition2D(regInf.getRadiusFirstRow(), -localYEdgeIROC), Sector(0));
288
289 const int stacks = sizeof(GEMFrameParameters<DataT>::POSTOP) / sizeof(GEMFrameParameters<DataT>::POSTOP[0]);
290
291 std::vector<DataT> radii;
292 for (int stack = 0; stack < stacks; ++stack) {
293 int region = 3;
294 if (stack == 1) {
295 region = 5;
296 } else if (stack == 2) {
297 region = 7;
298 } else if (stack == 3) {
299 region = 9;
301 const auto& regInf = Mapper::instance().getPadRegionInfo(region);
302 const float localYEdge = regInf.getPadsInRowRegion(region) / 2 * regInf.getPadWidth();
303 radii.emplace_back(std::sqrt(std::pow(GEMFrameParameters<DataT>::POSTOP[stack], 2) + std::pow(localYEdge, 2)));
304 }
305
306 std::vector<std::pair<size_t, float>> potentialInd;
307 const float weight = 1;
308 for (size_t iR = rStart; iR < rEnd; ++iR) {
309 const DataT radius = getRVertex(iR, side);
310 auto const it = std::lower_bound(radii.begin(), radii.end(), radius);
311 const int stack = (it == radii.end()) ? (stacks - 1) : (it - radii.begin());
313 // for stack 4 use the the number of phi bins at the edge
314 const auto radiusCompare = (stack == 4) ? GEMFrameParameters<DataT>::POSTOP[stack] : GEMFrameParameters<DataT>::POSTOP[stack] + (GEMFrameParameters<DataT>::POSTOP[stack] - GEMFrameParameters<DataT>::POSTOP[stack]) / 2;
315 for (size_t iPhiTmp = 0; iPhiTmp < getNPhiVertices(); ++iPhiTmp) {
316 const float margin = 0.5;
317 const DataT offsetGlobalY = radiusCompare * iPhiTmp * getGridSpacingPhi(side) + margin;
318 if (iPhiTmp > 0 && offsetGlobalY > globalPosEdgeIROC.Y()) {
319 break;
320 }
322 for (int sector = 0; sector < MGParameters::normalizeGridToNSector; ++sector) {
323 const size_t iPhiLeft = sector * verticesPerSector + iPhiTmp;
324 const size_t iZ = mParamGrid.NZVertices - 1;
325 potentialInd.emplace_back(mPotential[side].getDataIndex(iZ, iR, iPhiLeft), weight);
326 if (iPhiTmp > 0) {
327 const size_t iPhiRight = (sector + 1) * verticesPerSector - iPhiTmp;
328 potentialInd.emplace_back(mPotential[side].getDataIndex(iZ, iR, iPhiRight), weight);
329 }
331 }
332 }
333 std::sort(potentialInd.begin(), potentialInd.end());
334 return potentialInd;
335}
336
337template <typename DataT>
338void SpaceCharge<DataT>::setPotentialBoundaryGEMFrameAlongPhi(const std::function<DataT(DataT)>& potentialFunc, const GEMstack stack, const bool bottom, const Side side, const bool outerFrame)
339{
340 initContainer(mPotential[side], true);
341 const auto indices = getPotentialBoundaryGEMFrameAlongPhiIndices(stack, bottom, side, outerFrame);
342 setBoundaryFromIndices(potentialFunc, indices, side);
343}
344
345template <typename DataT>
346void SpaceCharge<DataT>::setBoundaryFromIndices(const std::function<DataT(DataT)>& potentialFunc, const std::vector<std::pair<size_t, float>>& indices, const Side side)
347{
348 /*
349 make check for the weights
350 Loop over bins in the radial direction
351 Check for duplicates and use the one with larger weight
352 */
353
354 for (const auto& indexw : indices) {
355 const int index = indexw.first;
356 const int iZ = mPotential[side].getIndexZ(index);
357 const int iR = mPotential[side].getIndexR(index);
358 const int iPhi = mPotential[side].getIndexPhi(index);
359 const DataT radius = getRVertex(iR, side);
360 const float weight = indexw.second;
361 const float pot = mPotential[side](iZ, iR, iPhi);
362 const float potNew = weight * potentialFunc(radius);
363 if (std::abs(potNew) > std::abs(pot)) {
364 mPotential[side](iZ, iR, iPhi) = potNew;
366 }
367}
368
369template <typename DataT>
370std::vector<std::pair<size_t, float>> SpaceCharge<DataT>::getPotentialBoundaryGEMFrameAlongPhiIndices(const GEMstack stack, const bool bottom, const Side side, const bool outerFrame, const bool noGap) const
371{
372 // to avoid double counting
373 auto indices = getPotentialBoundaryGEMFrameAlongRIndices(side);
374
375 int region = 0;
376 float offsStart = 0;
377 float offsEnd = 0;
378 if (bottom) {
379 offsEnd = -0.5;
381 region = 0;
382 } else if (stack == GEMstack::OROC1gem) {
383 region = 4;
384 } else if (stack == GEMstack::OROC2gem) {
385 region = 6;
386 } else if (stack == GEMstack::OROC3gem) {
387 region = 8;
388 }
389 } else {
390 offsStart = 0.5;
391 if (stack == GEMstack::IROCgem) {
392 region = 3;
393 } else if (stack == GEMstack::OROC1gem) {
394 region = 5;
395 } else if (stack == GEMstack::OROC2gem) {
396 region = 7;
397 } else if (stack == GEMstack::OROC3gem) {
398 region = 9;
399 }
402 const auto& regInf = Mapper::instance().getPadRegionInfo(region);
403 const auto radiusFirstRow = regInf.getRadiusFirstRow();
404 DataT radiusStart = bottom ? GEMFrameParameters<DataT>::POSBOTTOM[stack] : radiusFirstRow + regInf.getPadHeight() * regInf.getNumberOfPadRows();
405 if (bottom && (stack != GEMstack::IROCgem)) {
406 radiusStart -= 0.5;
407 }
408
409 auto radiusMax = bottom ? radiusFirstRow : GEMFrameParameters<DataT>::POSTOP[stack];
410
411 // add marging for first last padrows close to gap to improve numerical stabillity there
412 radiusStart += offsStart;
413 radiusMax += offsEnd;
414
415 int nVerticesR = (radiusMax - radiusStart) / getGridSpacingR(side);
416 if (nVerticesR == 0) {
417 nVerticesR = 1;
418 }
419
420 std::vector<std::pair<size_t, float>> potentialInd; // index, weight
421 const int verticesPerSector = mParamGrid.NPhiVertices / MGParameters::normalizeGridToNSector;
422 const auto nBinsPhi = (outerFrame || noGap) ? 0 : getPhiBinsGapFrame(side);
423 for (int sector = 0; sector < MGParameters::normalizeGridToNSector; ++sector) {
424 const auto offsetPhi = sector * verticesPerSector + verticesPerSector / 2;
425 for (size_t iPhiLocal = 0; iPhiLocal <= (verticesPerSector / 2 - nBinsPhi); ++iPhiLocal) {
426 const auto iPhiLeft = offsetPhi + iPhiLocal;
427 const auto iPhiRight = offsetPhi - iPhiLocal;
428 const DataT phiLeft = getPhiVertex(iPhiLeft, side);
429 const DataT phiRight = getPhiVertex(iPhiRight, side);
430 const DataT localphi = getPhiVertex(iPhiLocal, side);
431 const DataT radiusBottom = radiusStart / std::cos(localphi);
432 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
433 auto nREnd = (outerFrame && (stack == GEMstack::OROC3gem)) ? mParamGrid.NRVertices - 1 : rStart + nVerticesR;
434
435 // end at gem frame
436 if ((outerFrame && (stack == GEMstack::IROCgem))) {
437 // TODO: remove this?
438 const float marginCM = 0; // 0.4;
439 const int nMargingBins = marginCM / getGridSpacingR(side) + 0.5;
440 nREnd = (radiusBottom - getRVertex(1, side) + 0.5) / getGridSpacingR(side) + nMargingBins; // 2 safety margin
441 radiusMax = 3 + getRVertex(getNearestRVertex(radiusBottom + getGridSpacingR(side) * nMargingBins, side), side);
443
444 rStart -= 1;
445 nREnd += 1;
446 if (rStart <= 0) {
447 rStart = 1;
448 }
449
450 if (nREnd >= mParamGrid.NRVertices) {
451 nREnd = mParamGrid.NRVertices - 1;
452 }
453
454 float lxMin = radiusStart;
455 if ((outerFrame && (stack == GEMstack::IROCgem))) {
456 lxMin = 0;
457 }
458 const float lxMax = (nREnd == mParamGrid.NRVertices - 1) ? 9999 : radiusMax;
459 for (size_t iR = rStart; iR < nREnd; ++iR) {
460 const size_t iZ = mParamGrid.NZVertices - 1;
461 float weight = 1;
462 if (iPhiLeft < getNPhiVertices()) {
463 if (noGap || !std::binary_search(indices.begin(), indices.end(), std::make_pair(mPotential[side].getDataIndex(iZ, iR, iPhiLeft), 0.0f), [](const auto& a, const auto& b) { return (a.first < b.first); })) {
464
465 // check how much of the bin is in the lx range and assign weigth
466 const int nIterPoints = 1000;
467 int nPointsGood = 0;
468 for (int i = 0; i < nIterPoints; ++i) {
469 const float radius = getRVertex(iR, side) + getGridSpacingR(side) * gRandom->Uniform(-0.5, 0.5);
470 const float phi = getGridSpacingPhi(side) * gRandom->Uniform(-0.5, 0.5);
471 const DataT lx = radius * std::cos(phi + localphi);
472 if ((lx >= lxMin) && (lx <= lxMax)) {
473 ++nPointsGood;
474 }
475 }
476 weight = nPointsGood / double(nIterPoints);
477 potentialInd.emplace_back(mPotential[side].getDataIndex(iZ, iR, iPhiLeft), weight);
478 }
479 }
480
481 if (iPhiLocal && (noGap || !std::binary_search(indices.begin(), indices.end(), std::make_pair(mPotential[side].getDataIndex(iZ, iR, iPhiRight), 0.0f), [](const auto& a, const auto& b) { return (a.first < b.first); }))) {
482 potentialInd.emplace_back(mPotential[side].getDataIndex(iZ, iR, iPhiRight), weight);
483 }
484 }
485 }
486 }
487 // remove duplicate entries
488 std::sort(potentialInd.begin(), potentialInd.end());
489
490 // Remove duplicates
491 potentialInd.erase(std::unique(potentialInd.begin(), potentialInd.end()), potentialInd.end());
492
493 return potentialInd;
494}
495
496template <typename DataT>
497void SpaceCharge<DataT>::setPotentialBoundaryInnerRadius(const std::function<DataT(DataT)>& potentialFunc, const Side side)
498{
499 initContainer(mPotential[side], true);
500 for (size_t iZ = 0; iZ < mParamGrid.NZVertices; ++iZ) {
501 const DataT z = getZVertex(iZ, side);
502 const auto pot = potentialFunc(z);
503 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
504 const size_t iR = 0;
505 mPotential[side](iZ, iR, iPhi) += pot;
506 }
507 }
508}
509
510template <typename DataT>
511void SpaceCharge<DataT>::setPotentialBoundaryOuterRadius(const std::function<DataT(DataT)>& potentialFunc, const Side side)
512{
513 initContainer(mPotential[side], true);
514 for (size_t iZ = 0; iZ < mParamGrid.NZVertices; ++iZ) {
515 const DataT z = getZVertex(iZ, side);
516 const auto pot = potentialFunc(z);
517 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
518 const size_t iR = mParamGrid.NRVertices - 1;
519 mPotential[side](iZ, iR, iPhi) = pot;
520 }
521 }
522}
523
524template <typename DataT>
527 const Side side = formulaStruct.getSide();
528 initContainer(mPotential[side], true);
529 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
530 const DataT phi = getPhiVertex(iPhi, side);
531 for (size_t iR = 0; iR < mParamGrid.NRVertices; ++iR) {
532 const DataT radius = getRVertex(iR, side);
533 for (size_t iZ = 0; iZ < mParamGrid.NZVertices; ++iZ) {
534 const DataT z = getZVertex(iZ, side);
535 mPotential[side](iZ, iR, iPhi) = formulaStruct.evalPotential(z, radius, phi);
536 }
537 }
538 }
539}
540
541template <typename DataT>
542void SpaceCharge<DataT>::mirrorPotential(const Side sideRef, const Side sideMirrored)
543{
544 initContainer(mPotential[sideRef], true);
545 initContainer(mPotential[sideMirrored], true);
546 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
547 for (size_t iR = 0; iR < mParamGrid.NRVertices; ++iR) {
548 for (size_t iZ = 0; iZ < mParamGrid.NZVertices; ++iZ) {
549 mPotential[sideMirrored](iZ, iR, iPhi) = mPotential[sideRef](iZ, iR, iPhi);
550 }
551 }
552 }
554
555template <typename DataT>
557{
558 const Side side = formulaStruct.getSide();
559 initContainer(mPotential[side], true);
560 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
561 const DataT phi = getPhiVertex(iPhi, side);
562 for (size_t iZ = 0; iZ < mParamGrid.NZVertices; ++iZ) {
563 const DataT z = getZVertex(iZ, side);
564 const size_t iR = 0;
565 const DataT radius = getRVertex(iR, side);
566 mPotential[side](iZ, iR, iPhi) = formulaStruct.evalPotential(z, radius, phi);
567 }
568 }
569
570 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
571 const DataT phi = getPhiVertex(iPhi, side);
572 for (size_t iZ = 0; iZ < mParamGrid.NZVertices; ++iZ) {
573 const DataT z = getZVertex(iZ, side);
574 const size_t iR = mParamGrid.NRVertices - 1;
575 const DataT radius = getRVertex(iR, side);
576 mPotential[side](iZ, iR, iPhi) = formulaStruct.evalPotential(z, radius, phi);
577 }
578 }
579
580 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
581 const DataT phi = getPhiVertex(iPhi, side);
582 for (size_t iR = 0; iR < mParamGrid.NRVertices; ++iR) {
583 const DataT radius = getRVertex(iR, side);
584 const size_t iZ = 0;
585 const DataT z = getZVertex(iZ, side);
586 mPotential[side](iZ, iR, iPhi) = formulaStruct.evalPotential(z, radius, phi);
587 }
588 }
590 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
591 const DataT phi = getPhiVertex(iPhi, side);
592 for (size_t iR = 0; iR < mParamGrid.NRVertices; ++iR) {
593 const DataT radius = getRVertex(iR, side);
594 const size_t iZ = mParamGrid.NZVertices - 1;
595 const DataT z = getZVertex(iZ, side);
596 mPotential[side](iZ, iR, iPhi) = formulaStruct.evalPotential(z, radius, phi);
597 }
598 }
599}
600
601template <typename DataT>
602void SpaceCharge<DataT>::poissonSolver(const Side side, const DataT stoppingConvergence, const int symmetry)
603{
604 initContainer(mDensity[side], true);
605 initContainer(mPotential[side], true);
607 PoissonSolver<DataT> poissonSolver(mGrid3D[0]);
608 poissonSolver.poissonSolver3D(mPotential[side], mDensity[side], symmetry);
609}
610
611template <typename DataT>
612void SpaceCharge<DataT>::poissonSolver(const DataT stoppingConvergence, const int symmetry)
613{
614#pragma omp parallel for num_threads(2)
615 for (int iside = 0; iside < FNSIDES; ++iside) {
616 const o2::tpc::Side side = (iside == 0) ? Side::A : Side::C;
617 poissonSolver(side, stoppingConvergence, symmetry);
618 }
619}
620
621template <typename DataT>
623{
624 const Side side = formulaStruct.getSide();
625 initContainer(mElectricFieldEr[side], true);
626 initContainer(mElectricFieldEz[side], true);
627 initContainer(mElectricFieldEphi[side], true);
628 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
629 for (size_t iR = 0; iR < mParamGrid.NRVertices; ++iR) {
630 for (size_t iZ = 0; iZ < mParamGrid.NZVertices; ++iZ) {
631 const DataT radius = getRVertex(iR, side);
632 const DataT z = getZVertex(iZ, side);
633 const DataT phi = getPhiVertex(iPhi, side);
634 mElectricFieldEr[side](iZ, iR, iPhi) = formulaStruct.evalFieldR(z, radius, phi);
635 mElectricFieldEz[side](iZ, iR, iPhi) = formulaStruct.evalFieldZ(z, radius, phi);
636 mElectricFieldEphi[side](iZ, iR, iPhi) = formulaStruct.evalFieldPhi(z, radius, phi);
637 }
638 }
640}
641
642template <typename DataT>
644{
645 using timer = std::chrono::high_resolution_clock;
646 auto start = timer::now();
647 initContainer(mPotential[side], true);
648 initContainer(mElectricFieldEr[side], true);
649 initContainer(mElectricFieldEz[side], true);
650 initContainer(mElectricFieldEphi[side], true);
651#pragma omp parallel for num_threads(sNThreads)
652 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
653 const int symmetry = 0;
654 size_t tmpPlus = iPhi + 1;
655 int signPlus = 1;
656 int tmpMinus = static_cast<int>(iPhi - 1);
657 int signMinus = 1;
658 if (symmetry == 1 || symmetry == -1) { // Reflection symmetry in phi (e.g. symmetry at sector boundaries, or half sectors, etc.)
659 if (tmpPlus > mParamGrid.NPhiVertices - 1) {
660 if (symmetry == -1) {
661 signPlus = -1;
662 }
663 tmpPlus = mParamGrid.NPhiVertices - 2;
664 }
665 if (tmpMinus < 0) {
666 tmpMinus = 1; // SHOULD IT BE =0?
667 if (symmetry == -1) {
668 signMinus = -1;
669 }
670 }
671 } else { // No Symmetries in phi, no boundaries, the calculations is continuous across all phi
672 if (tmpPlus > mParamGrid.NPhiVertices - 1) {
673 tmpPlus = iPhi + 1 - mParamGrid.NPhiVertices;
674 }
675 if (tmpMinus < 0) {
676 tmpMinus = static_cast<int>(iPhi - 1 + mParamGrid.NPhiVertices);
677 }
678 }
679
680 // for non-boundary V
681 for (size_t iR = 1; iR < mParamGrid.NRVertices - 1; iR++) {
682 const DataT radius = getRVertex(iR, side);
683 for (size_t iZ = 1; iZ < mParamGrid.NZVertices - 1; iZ++) {
684 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
685 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
686 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
687 }
689
690 // for boundary-r
691 for (size_t iZ = 0; iZ < mParamGrid.NZVertices; iZ++) {
692 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
693 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
694 }
695
696 for (size_t iR = 0; iR < mParamGrid.NRVertices; iR += mParamGrid.NRVertices - 1) {
697 const DataT radius = getRVertex(iR, side);
698 for (size_t iZ = 1; iZ < mParamGrid.NZVertices - 1; iZ++) {
699 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
700 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
701 }
702 }
703
704 // for boundary-z
705 for (size_t iR = 0; iR < mParamGrid.NRVertices; ++iR) {
706 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);
707 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);
708 }
709
710 for (size_t iR = 1; iR < mParamGrid.NRVertices - 1; ++iR) {
711 const DataT radius = getRVertex(iR, side);
712 for (size_t iZ = 0; iZ < mParamGrid.NZVertices; iZ += mParamGrid.NZVertices - 1) {
713 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
714 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
715 }
716 }
717
718 // corner points for EPhi
719 for (size_t iR = 0; iR < mParamGrid.NRVertices; iR += mParamGrid.NRVertices - 1) {
720 const DataT radius = getRVertex(iR, side);
721 for (size_t iZ = 0; iZ < mParamGrid.NZVertices; iZ += mParamGrid.NZVertices - 1) {
722 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
723 }
724 }
725 }
726 auto stop = timer::now();
727 std::chrono::duration<float> time = stop - start;
728 const float totalTime = time.count();
729 LOGP(detail, "electric field calculation took {}s", totalTime);
730}
731
732template <typename DataT>
733void 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)
734{
735 calcGlobalDistCorrIterative(globCorr, maxIter, approachZ, approachR, approachPhi, diffCorr, scSCale, scale, Type::Distortions);
736}
737
738template <typename DataT>
739void 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)
740{
741 calcGlobalDistCorrIterative(getGlobalCorrInterpolator(side), maxIter, approachZ, approachR, approachPhi, diffCorr, scSCale, scale, Type::Distortions);
742}
743
744template <typename DataT>
745void SpaceCharge<DataT>::calcGlobalDistWithGlobalCorrIterative(const SpaceCharge<DataT>* scSCale, float scale, const int maxIter, const DataT approachZ, const DataT approachR, const DataT approachPhi, const DataT diffCorr)
746{
747#pragma omp parallel for num_threads(sNThreads)
748 for (int iside = 0; iside < FNSIDES; ++iside) {
749 const o2::tpc::Side side = (iside == 0) ? Side::A : Side::C;
750 calcGlobalDistWithGlobalCorrIterative(side, scSCale, scale, maxIter, approachZ, approachR, approachPhi, diffCorr);
751 }
752}
753
754template <typename DataT>
755void 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)
756{
757 calcGlobalDistCorrIterative(getGlobalDistInterpolator(side), maxIter, approachZ, approachR, approachPhi, diffCorr, scSCale, scale, Type::Corrections);
758}
759
760template <typename DataT>
761void SpaceCharge<DataT>::calcGlobalCorrWithGlobalDistIterative(const SpaceCharge<DataT>* scSCale, float scale, const int maxIter, const DataT approachZ, const DataT approachR, const DataT approachPhi, const DataT diffCorr)
762{
763#pragma omp parallel for num_threads(sNThreads)
764 for (int iside = 0; iside < FNSIDES; ++iside) {
765 const o2::tpc::Side side = (iside == 0) ? Side::A : Side::C;
766 calcGlobalCorrWithGlobalDistIterative(side, scSCale, scale, maxIter, approachZ, approachR, approachPhi, diffCorr);
767 }
768}
769
770template <typename DataT>
771void 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)
772{
773 const Side side = globCorr.getSide();
774 if (type == Type::Distortions) {
775 initContainer(mGlobalDistdR[side], true);
776 initContainer(mGlobalDistdZ[side], true);
777 initContainer(mGlobalDistdRPhi[side], true);
778 } else {
779 initContainer(mGlobalCorrdR[side], true);
780 initContainer(mGlobalCorrdZ[side], true);
781 initContainer(mGlobalCorrdRPhi[side], true);
782 }
783
784 const auto& scSCaleInterpolator = (type == Type::Distortions) ? scSCale->mInterpolatorGlobalCorr[side] : scSCale->mInterpolatorGlobalDist[side];
785
786#pragma omp parallel for num_threads(sNThreads)
787 for (unsigned int iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
788 const DataT phi = getPhiVertex(iPhi, side);
789 for (unsigned int iR = 0; iR < mParamGrid.NRVertices; ++iR) {
790 const DataT radius = getRVertex(iR, side);
791 for (unsigned int iZ = 1; iZ < mParamGrid.NZVertices; ++iZ) {
792 const DataT z = getZVertex(iZ, side);
793 //
794 //==========================================================================================
795 //==== start algorithm: use tricubic upsampling to numerically approach the query point ====
796 //==========================================================================================
797 //
798 // 1. calculate difference from nearest point to query point with stepwidth factor x
799 // and approach the new point
800 //
801 DataT stepR = 0;
802 DataT stepZ = 0;
803 DataT stepPhi = 0;
804
805 // needed to check for convergence
806 DataT lastCorrdR = std::numeric_limits<DataT>::max();
807 DataT lastCorrdZ = std::numeric_limits<DataT>::max();
808 DataT lastCorrdRPhi = std::numeric_limits<DataT>::max();
809
810 // interpolated global correction
811 DataT corrdR = 0;
812 DataT corrdRPhi = 0;
813 DataT corrdZ = 0;
814
815 for (int iter = 0; iter < maxIter; ++iter) {
816 // 2. get new point coordinates
817 const DataT rCurrPos = radius + stepR;
818 const DataT zCurrPos = z + stepZ;
819 const DataT phiCurrPos = phi + stepPhi;
820
821 // abort calculation of drift path if electron reached inner/outer field cage or central electrode
822 if (rCurrPos <= getRMinSim(side) || rCurrPos >= getRMaxSim(side) || getSide(zCurrPos) != side) {
823 break;
824 }
825
826 // interpolate global correction at new point and calculate position of global correction
827 corrdR = globCorr.evaldR(zCurrPos, rCurrPos, phiCurrPos);
828 if (scSCale && scale != 0) {
829 corrdR += scale * scSCaleInterpolator.evaldR(zCurrPos, rCurrPos, phiCurrPos);
830 }
831 const DataT rNewPos = rCurrPos + corrdR;
832
833 DataT corrPhi = 0;
834 if (scSCale && scale != 0) {
835 corrPhi = scale * scSCaleInterpolator.evaldRPhi(zCurrPos, rCurrPos, phiCurrPos);
836 }
837 corrPhi += globCorr.evaldRPhi(zCurrPos, rCurrPos, phiCurrPos);
838 corrPhi /= rCurrPos;
839
840 corrdRPhi = corrPhi * rNewPos; // normalize to new r coordinate
841 const DataT phiNewPos = phiCurrPos + corrPhi;
842
843 corrdZ = globCorr.evaldZ(zCurrPos, rCurrPos, phiCurrPos);
844 if (scSCale && scale != 0) {
845 corrdZ += scale * scSCaleInterpolator.evaldZ(zCurrPos, rCurrPos, phiCurrPos);
846 }
847 const DataT zNewPos = zCurrPos + corrdZ;
848
849 // approach desired coordinate
850 stepR += (radius - rNewPos) * approachR;
851 stepZ += (z - zNewPos) * approachZ;
852 stepPhi += (phi - phiNewPos) * approachPhi;
853
854 // check for convergence
855 const DataT diffCorrdR = std::abs(corrdR - lastCorrdR);
856 const DataT diffCorrdRZ = std::abs(corrdZ - lastCorrdZ);
857 const DataT diffCorrdRPhi = std::abs(corrdRPhi - lastCorrdRPhi);
858
859 // stop algorithm if converged
860 if (diffCorrdR < diffCorr && diffCorrdRZ < diffCorr && diffCorrdRPhi < diffCorr) {
861 break;
862 }
863
864 lastCorrdR = corrdR;
865 lastCorrdZ = corrdZ;
866 lastCorrdRPhi = corrdRPhi;
867 }
868 // set global distortions if algorithm converged or iterations exceed max numbers of iterations
869 if (type == Type::Distortions) {
870 mGlobalDistdR[side](iZ, iR, iPhi) = -corrdR;
871 mGlobalDistdRPhi[side](iZ, iR, iPhi) = -corrdRPhi;
872 mGlobalDistdZ[side](iZ, iR, iPhi) = -corrdZ;
873 } else {
874 mGlobalCorrdR[side](iZ, iR, iPhi) = -corrdR;
875 mGlobalCorrdRPhi[side](iZ, iR, iPhi) = -corrdRPhi;
876 mGlobalCorrdZ[side](iZ, iR, iPhi) = -corrdZ;
877 }
878 }
879 }
880 for (unsigned int iR = 0; iR < mParamGrid.NRVertices; ++iR) {
881 if (type == Type::Distortions) {
882 mGlobalDistdR[side](0, iR, iPhi) = 3 * (mGlobalDistdR[side](1, iR, iPhi) - mGlobalDistdR[side](2, iR, iPhi)) + mGlobalDistdR[side](3, iR, iPhi);
883 mGlobalDistdRPhi[side](0, iR, iPhi) = 3 * (mGlobalDistdRPhi[side](1, iR, iPhi) - mGlobalDistdRPhi[side](2, iR, iPhi)) + mGlobalDistdRPhi[side](3, iR, iPhi);
884 mGlobalDistdZ[side](0, iR, iPhi) = 3 * (mGlobalDistdZ[side](1, iR, iPhi) - mGlobalDistdZ[side](2, iR, iPhi)) + mGlobalDistdZ[side](3, iR, iPhi);
885 } else {
886 mGlobalCorrdR[side](0, iR, iPhi) = 3 * (mGlobalCorrdR[side](1, iR, iPhi) - mGlobalCorrdR[side](2, iR, iPhi)) + mGlobalCorrdR[side](3, iR, iPhi);
887 mGlobalCorrdRPhi[side](0, iR, iPhi) = 3 * (mGlobalCorrdRPhi[side](1, iR, iPhi) - mGlobalCorrdRPhi[side](2, iR, iPhi)) + mGlobalCorrdRPhi[side](3, iR, iPhi);
888 mGlobalCorrdZ[side](0, iR, iPhi) = 3 * (mGlobalCorrdZ[side](1, iR, iPhi) - mGlobalCorrdZ[side](2, iR, iPhi)) + mGlobalCorrdZ[side](3, iR, iPhi);
889 }
890 }
891 }
892}
893
894template <typename DataT>
895void 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)
896{
897 calcGlobalDistCorrIterativeLinearCartesian(getGlobalCorrInterpolator(side), maxIter, approachX, approachY, approachZ, diffCorr, scSCale, scale, Type::Distortions);
898}
899
900template <typename DataT>
901void SpaceCharge<DataT>::calcGlobalDistWithGlobalCorrIterativeLinearCartesian(const SpaceCharge<DataT>* scSCale, float scale, const int maxIter, const DataT approachX, const DataT approachY, const DataT approachZ, const DataT diffCorr)
902{
903#pragma omp parallel for num_threads(sNThreads)
904 for (int iside = 0; iside < FNSIDES; ++iside) {
905 const o2::tpc::Side side = (iside == 0) ? Side::A : Side::C;
906 calcGlobalDistWithGlobalCorrIterativeLinearCartesian(side, scSCale, scale, maxIter, approachX, approachY, approachZ, diffCorr);
907 }
908}
909
910template <typename DataT>
911void 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)
912{
913 calcGlobalDistCorrIterativeLinearCartesian(getGlobalDistInterpolator(side), maxIter, approachX, approachY, approachZ, diffCorr, scSCale, scale, Type::Corrections);
914}
915
916template <typename DataT>
917void SpaceCharge<DataT>::calcGlobalCorrWithGlobalDistIterativeLinearCartesian(const SpaceCharge<DataT>* scSCale, float scale, const int maxIter, const DataT approachX, const DataT approachY, const DataT approachZ, const DataT diffCorr)
918{
919#pragma omp parallel for num_threads(sNThreads)
920 for (int iside = 0; iside < FNSIDES; ++iside) {
921 const o2::tpc::Side side = (iside == 0) ? Side::A : Side::C;
922 calcGlobalCorrWithGlobalDistIterativeLinearCartesian(side, scSCale, scale, maxIter, approachX, approachY, approachZ, diffCorr);
923 }
924}
925
926template <typename DataT>
927void 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)
928{
929 const Side side = globCorr.getSide();
930 if (type == Type::Distortions) {
931 initContainer(mGlobalDistdR[side], true);
932 initContainer(mGlobalDistdZ[side], true);
933 initContainer(mGlobalDistdRPhi[side], true);
934 } else {
935 initContainer(mGlobalCorrdR[side], true);
936 initContainer(mGlobalCorrdZ[side], true);
937 initContainer(mGlobalCorrdRPhi[side], true);
938 }
939
940 const auto& scSCaleInterpolator = (type == Type::Distortions) ? scSCale->mInterpolatorGlobalCorr[side] : scSCale->mInterpolatorGlobalDist[side];
941
942#pragma omp parallel for num_threads(sNThreads)
943 for (unsigned int iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
944 const DataT phi = getPhiVertex(iPhi, side);
945 for (unsigned int iR = 0; iR < mParamGrid.NRVertices; ++iR) {
946 const DataT radius = getRVertex(iR, side);
947 const DataT x = getXFromPolar(radius, phi);
948 const DataT y = getYFromPolar(radius, phi);
949
950 for (unsigned int iZ = 1; iZ < mParamGrid.NZVertices; ++iZ) {
951 const DataT z = getZVertex(iZ, side);
952
953 DataT stepX = 0;
954 DataT stepY = 0;
955 DataT stepZ = 0;
956
957 // needed to check for convergence
958 DataT lastCorrX = std::numeric_limits<DataT>::max();
959 DataT lastCorrY = std::numeric_limits<DataT>::max();
960 DataT lastCorrZ = std::numeric_limits<DataT>::max();
961 DataT lastX = std::numeric_limits<DataT>::max();
962 DataT lastY = std::numeric_limits<DataT>::max();
963 DataT lastZ = std::numeric_limits<DataT>::max();
964
965 for (int iter = 0; iter < maxIter; ++iter) {
966 const DataT xCurrPos = x + stepX;
967 const DataT yCurrPos = y + stepY;
968 const DataT zCurrPos = z + stepZ;
969
970 // abort calculation of drift path if electron reached inner/outer field cage or central electrode
971 const DataT rCurrPos = getRadiusFromCartesian(xCurrPos, yCurrPos);
972 if (rCurrPos <= getRMinSim(side) || rCurrPos >= getRMaxSim(side) || getSide(zCurrPos) != side) {
973 break;
974 }
975
976 // interpolate global correction at new point and calculate position of global correction
977 DataT corrX = 0;
978 DataT corrY = 0;
979 DataT corrZ = 0;
980 (type == Type::Distortions) ? getCorrections(xCurrPos, yCurrPos, zCurrPos, side, corrX, corrY, corrZ) : getDistortions(xCurrPos, yCurrPos, zCurrPos, side, corrX, corrY, corrZ);
981
982 if (scSCale && scale != 0) {
983 DataT corrXScale = 0;
984 DataT corrYScale = 0;
985 DataT corrZScale = 0;
986 (type == Type::Distortions) ? scSCale->getCorrections(xCurrPos, yCurrPos, zCurrPos, side, corrXScale, corrYScale, corrZScale) : getDistortions(xCurrPos, yCurrPos, zCurrPos, side, corrX, corrY, corrZ);
987 corrX += scale * corrXScale;
988 corrY += scale * corrYScale;
989 corrZ += scale * corrZScale;
990 }
991
992 // check for convergence
993 const DataT diffCorrX = std::abs(corrX - lastCorrX);
994 const DataT diffCorrY = std::abs(corrY - lastCorrY);
995 const DataT diffCorrZ = std::abs(corrZ - lastCorrZ);
996
997 lastCorrX = corrX;
998 lastCorrY = corrY;
999 lastCorrZ = corrZ;
1000 lastX = xCurrPos;
1001 lastY = yCurrPos;
1002 lastZ = zCurrPos;
1003
1004 // stop algorithm if converged
1005 if ((diffCorrX < diffCorr) && (diffCorrY < diffCorr) && (diffCorrZ < diffCorr)) {
1006 break;
1007 }
1008
1009 const DataT xNewPos = xCurrPos + corrX;
1010 const DataT yNewPos = yCurrPos + corrY;
1011 const DataT zNewPos = zCurrPos + corrZ;
1012
1013 // approach desired coordinate
1014 stepX += (x - xNewPos) * approachX;
1015 stepY += (y - yNewPos) * approachY;
1016 stepZ += (z - zNewPos) * approachZ;
1017 }
1018
1019 const DataT xNew = lastX + lastCorrX;
1020 const DataT yNew = lastY + lastCorrY;
1021 const DataT radiusNew = getRadiusFromCartesian(xNew, yNew);
1022 const DataT corrdR = -(radiusNew - getRadiusFromCartesian(lastX, lastY));
1023
1024 float phiNew = getPhiFromCartesian(xNew, yNew);
1026
1027 float phiLast = getPhiFromCartesian(lastX, lastY);
1029
1030 DataT deltaPhi = (phiNew - phiLast);
1031 // handle edge cases
1032 if (deltaPhi > PI) {
1033 deltaPhi -= 2 * PI;
1034 } else if (deltaPhi < -PI) {
1035 deltaPhi += 2 * PI;
1036 }
1037 const DataT corrdRPhi = -deltaPhi * radiusNew;
1038
1039 // set global distortions if algorithm converged or iterations exceed max numbers of iterations
1040 if (type == Type::Distortions) {
1041 mGlobalDistdR[side](iZ, iR, iPhi) = corrdR;
1042 mGlobalDistdRPhi[side](iZ, iR, iPhi) = corrdRPhi;
1043 mGlobalDistdZ[side](iZ, iR, iPhi) = -lastCorrZ;
1044 } else {
1045 mGlobalCorrdR[side](iZ, iR, iPhi) = corrdR;
1046 mGlobalCorrdRPhi[side](iZ, iR, iPhi) = corrdRPhi;
1047 mGlobalCorrdZ[side](iZ, iR, iPhi) = -lastCorrZ;
1048 }
1049 }
1050 }
1051 for (unsigned int iR = 0; iR < mParamGrid.NRVertices; ++iR) {
1052 if (type == Type::Distortions) {
1053 mGlobalDistdR[side](0, iR, iPhi) = 3 * (mGlobalDistdR[side](1, iR, iPhi) - mGlobalDistdR[side](2, iR, iPhi)) + mGlobalDistdR[side](3, iR, iPhi);
1054 mGlobalDistdRPhi[side](0, iR, iPhi) = 3 * (mGlobalDistdRPhi[side](1, iR, iPhi) - mGlobalDistdRPhi[side](2, iR, iPhi)) + mGlobalDistdRPhi[side](3, iR, iPhi);
1055 mGlobalDistdZ[side](0, iR, iPhi) = 3 * (mGlobalDistdZ[side](1, iR, iPhi) - mGlobalDistdZ[side](2, iR, iPhi)) + mGlobalDistdZ[side](3, iR, iPhi);
1056 } else {
1057 mGlobalCorrdR[side](0, iR, iPhi) = 3 * (mGlobalCorrdR[side](1, iR, iPhi) - mGlobalCorrdR[side](2, iR, iPhi)) + mGlobalCorrdR[side](3, iR, iPhi);
1058 mGlobalCorrdRPhi[side](0, iR, iPhi) = 3 * (mGlobalCorrdRPhi[side](1, iR, iPhi) - mGlobalCorrdRPhi[side](2, iR, iPhi)) + mGlobalCorrdRPhi[side](3, iR, iPhi);
1059 mGlobalCorrdZ[side](0, iR, iPhi) = 3 * (mGlobalCorrdZ[side](1, iR, iPhi) - mGlobalCorrdZ[side](2, iR, iPhi)) + mGlobalCorrdZ[side](3, iR, iPhi);
1060 }
1061 }
1062 }
1063}
1064
1065template <typename DataT>
1067{
1068 if (!mElectricFieldEr[side].getNDataPoints()) {
1069 LOGP(warning, "============== E-Fields are not set! ==============");
1070 }
1071 NumericalFields<DataT> numFields(mElectricFieldEr[side], mElectricFieldEz[side], mElectricFieldEphi[side], mGrid3D[side], side);
1072 return numFields;
1073}
1074
1075template <typename DataT>
1077{
1078 if (!mLocalDistdR[side].getNDataPoints()) {
1079 LOGP(warning, "============== local distortions not set! ==============");
1080 }
1081 DistCorrInterpolator<DataT> numFields(mLocalDistdR[side], mLocalDistdZ[side], mLocalDistdRPhi[side], mGrid3D[side], side);
1082 return numFields;
1083}
1084
1085template <typename DataT>
1087{
1088 if (!mLocalCorrdR[side].getNDataPoints()) {
1089 LOGP(warning, "============== local corrections not set! ==============");
1091 DistCorrInterpolator<DataT> numFields(mLocalCorrdR[side], mLocalCorrdZ[side], mLocalCorrdRPhi[side], mGrid3D[side], side);
1092 return numFields;
1093}
1094
1095template <typename DataT>
1097{
1098 if (!mGlobalDistdR[side].getNDataPoints()) {
1099 LOGP(warning, "============== global distortions not set ==============");
1100 }
1101 DistCorrInterpolator<DataT> numFields(mGlobalDistdR[side], mGlobalDistdZ[side], mGlobalDistdRPhi[side], mGrid3D[side], side);
1102 return numFields;
1103}
1104
1105template <typename DataT>
1107{
1108 if (!mGlobalCorrdR[side].getNDataPoints()) {
1109 LOGP(warning, "============== global corrections not set ==============");
1110 }
1111 DistCorrInterpolator<DataT> numFields(mGlobalCorrdR[side], mGlobalCorrdZ[side], mGlobalCorrdRPhi[side], mGrid3D[side], side);
1112 return numFields;
1113}
1114
1115template <typename DataT>
1117{
1118 TH3* hisSCDensity3D = (TH3*)fInp.Get(name);
1119 fillChargeDensityFromHisto(*hisSCDensity3D);
1120 delete hisSCDensity3D;
1121}
1122
1123template <typename DataT>
1125{
1126 const auto hConverted = o2::tpc::painter::convertCalDetToTH3(calSCDensity3D, true, mParamGrid.NRVertices, getRMin(Side::A), getRMax(Side::A), mParamGrid.NPhiVertices, getZMax(Side::A));
1127 fillChargeDensityFromHisto(hConverted);
1128}
1129
1130template <typename DataT>
1131void SpaceCharge<DataT>::fillChargeFromCalDet(const std::vector<CalDet<float>>& calCharge3D)
1132{
1133 auto hConverted = o2::tpc::painter::convertCalDetToTH3(calCharge3D, false, mParamGrid.NRVertices, getRMin(Side::A), getRMax(Side::A), mParamGrid.NPhiVertices, getZMax(Side::A));
1134 normalizeHistoQVEps0(hConverted);
1135 fillChargeDensityFromHisto(hConverted);
1136}
1137
1138template <typename DataT>
1141 const unsigned short nBinsZNew = mParamGrid.NZVertices;
1142 const unsigned short nBinsRNew = mParamGrid.NRVertices;
1143 const unsigned short nBinsPhiNew = mParamGrid.NPhiVertices;
1144
1145 initContainer(mDensity[Side::A], true);
1146 initContainer(mDensity[Side::C], true);
1147
1148 const int nBinsZNewTwo = 2 * nBinsZNew;
1149 const auto phiLow = hOrig.GetXaxis()->GetBinLowEdge(1);
1150 const auto phiUp = hOrig.GetXaxis()->GetBinUpEdge(hOrig.GetNbinsX());
1151 const auto rLow = hOrig.GetYaxis()->GetBinLowEdge(1);
1152 const auto rUp = hOrig.GetYaxis()->GetBinUpEdge(hOrig.GetNbinsY());
1153 const auto zLow = hOrig.GetZaxis()->GetBinLowEdge(1);
1154 const auto zUp = hOrig.GetZaxis()->GetBinUpEdge(hOrig.GetNbinsZ());
1156 const int dim = 3;
1157 int bins[dim]{nBinsPhiNew, nBinsRNew, nBinsZNewTwo};
1158 double xmin[dim]{phiLow, rLow, zLow};
1159 double xmax[dim]{phiUp, rUp, zUp};
1160 const THnSparseF hRebin("hTmp", "hTmp", dim, bins, xmin, xmax);
1162#pragma omp parallel for num_threads(sNThreads)
1163 for (int iBinPhi = 1; iBinPhi <= nBinsPhiNew; ++iBinPhi) {
1164 const auto phiLowEdge = hRebin.GetAxis(0)->GetBinLowEdge(iBinPhi);
1165 const auto phiUpEdge = hRebin.GetAxis(0)->GetBinUpEdge(iBinPhi);
1166
1167 const int phiLowBinOrig = hOrig.GetXaxis()->FindBin(phiLowEdge);
1168 const int phiUpBinOrig = hOrig.GetXaxis()->FindBin(phiUpEdge);
1169
1170 // calculate the weights (area of original bin lies in the new bin / binwidthOrig) of the first and last bins
1171 const auto binWidthPhiOrig = hOrig.GetXaxis()->GetBinWidth(phiLowBinOrig);
1172 const auto lowerBinWeightPhi = std::abs(phiLowEdge - hOrig.GetXaxis()->GetBinUpEdge(phiLowBinOrig)) / binWidthPhiOrig;
1173 const auto upperBinWeightPhi = std::abs(phiUpEdge - hOrig.GetXaxis()->GetBinLowEdge(phiUpBinOrig)) / binWidthPhiOrig;
1174
1175 for (int iBinR = 1; iBinR <= nBinsRNew; ++iBinR) {
1176 const auto rLowEdge = hRebin.GetAxis(1)->GetBinLowEdge(iBinR);
1177 const auto rUpEdge = hRebin.GetAxis(1)->GetBinUpEdge(iBinR);
1178
1179 const int rLowBinOrig = hOrig.GetYaxis()->FindBin(rLowEdge);
1180 const int rUpBinOrig = hOrig.GetYaxis()->FindBin(rUpEdge);
1181
1182 // calculate the weights (area of original bin lies in the new bin / binwidthOrig) of the first and last bins
1183 const auto binWidthROrig = hOrig.GetYaxis()->GetBinWidth(rLowBinOrig);
1184 const auto lowerBinWeightR = std::abs(rLowEdge - hOrig.GetYaxis()->GetBinUpEdge(rLowBinOrig)) / binWidthROrig;
1185 const auto upperBinWeightR = std::abs(rUpEdge - hOrig.GetYaxis()->GetBinLowEdge(rUpBinOrig)) / binWidthROrig;
1186
1187 for (int iBinZ = 1; iBinZ <= nBinsZNewTwo; ++iBinZ) {
1188 const auto zLowEdge = hRebin.GetAxis(2)->GetBinLowEdge(iBinZ);
1189 const auto zUpEdge = hRebin.GetAxis(2)->GetBinUpEdge(iBinZ);
1190 const auto zCenter = hRebin.GetAxis(2)->GetBinCenter(iBinZ);
1191
1192 int zLowBinOrig = hOrig.GetZaxis()->FindBin(zLowEdge);
1193 int zUpBinOrig = hOrig.GetZaxis()->FindBin(zUpEdge);
1194 const int currside = getSide(zCenter); // set the side of the current z-bin
1195 // get the side of the lowest and uppest bin from the orig histo
1196 const int sideLowOrig = getSide(hOrig.GetZaxis()->GetBinCenter(zLowBinOrig));
1197 const int sideUpOrig = getSide(hOrig.GetZaxis()->GetBinCenter(zUpBinOrig));
1198
1199 // make bounds/side check of the zLowBinOrig and zUpBinOrig bins. They must be on the same side as the currside!!!
1200 if (currside != sideLowOrig && zLowBinOrig != zUpBinOrig) {
1201 // 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
1202 bool notequal = true;
1203 do {
1204 zLowBinOrig += 1;
1205 if (zLowBinOrig > zUpBinOrig) {
1206 LOGP(warning, "SOMETHING WENT WRONG: SETTING BINS TO: {}", zUpBinOrig);
1207 zLowBinOrig = zUpBinOrig;
1208 notequal = false;
1210 const int sideTmp = getSide(hOrig.GetZaxis()->GetBinCenter(zLowBinOrig));
1211 if (sideTmp == currside) {
1212 notequal = false;
1213 }
1214 } while (notequal);
1216
1217 if (currside != sideUpOrig && zLowBinOrig != zUpBinOrig) {
1218 // 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
1219 bool notequal = true;
1220 do {
1221 zUpBinOrig -= 1;
1222 if (zUpBinOrig < zLowBinOrig) {
1223 LOGP(warning, "SOMETHING WENT WRONG: SETTING BINS TO: {}", zLowBinOrig);
1224 zUpBinOrig = zLowBinOrig;
1225 notequal = false;
1226 }
1227 const int sideTmp = getSide(hOrig.GetZaxis()->GetBinCenter(zUpBinOrig));
1228 if (sideTmp == currside) {
1229 notequal = false;
1230 }
1231 } while (notequal);
1232 }
1233
1234 const auto binWidthZOrig = hOrig.GetZaxis()->GetBinWidth(zLowBinOrig);
1235 const auto lowerBinWeightZ = std::abs(zLowEdge - hOrig.GetZaxis()->GetBinUpEdge(zLowBinOrig)) / binWidthZOrig;
1236 const auto upperBinWeightZ = std::abs(zUpEdge - hOrig.GetZaxis()->GetBinLowEdge(zUpBinOrig)) / binWidthZOrig;
1237
1238 // get the mean value of the original histogram of the found bin range
1239 DataT sum = 0;
1240 DataT sumW = 0;
1241 for (int iPhi = phiLowBinOrig; iPhi <= phiUpBinOrig; ++iPhi) {
1242 DataT weightPhi = 1;
1243 if (iPhi == phiLowBinOrig) {
1244 weightPhi = lowerBinWeightPhi;
1245 } else if (iPhi == phiUpBinOrig) {
1246 weightPhi = upperBinWeightPhi;
1247 }
1248
1249 for (int iR = rLowBinOrig; iR <= rUpBinOrig; ++iR) {
1250 DataT weightR = 1;
1251 if (iR == rLowBinOrig) {
1252 weightR = lowerBinWeightR;
1253 } else if (iR == rUpBinOrig) {
1254 weightR = upperBinWeightR;
1255 }
1257 for (int iZ = zLowBinOrig; iZ <= zUpBinOrig; ++iZ) {
1258 DataT weightZ = 1;
1259 if (iZ == zLowBinOrig) {
1260 weightZ = lowerBinWeightZ;
1261 } else if (iZ == zUpBinOrig) {
1262 weightZ = upperBinWeightZ;
1263 }
1264 const auto val = hOrig.GetBinContent(iPhi, iR, iZ);
1265 // if(val==0){
1266 // what to do now???
1267 // }
1268 const auto totalWeight = weightPhi * weightR * weightZ;
1269 sum += val * totalWeight;
1270 sumW += totalWeight;
1271 }
1272 }
1273 }
1274 sum /= sumW;
1275 const Side side = (iBinZ > mParamGrid.NZVertices) ? Side::A : Side::C;
1276 const int iZ = (side == Side::A) ? (iBinZ - mParamGrid.NZVertices - 1) : (mParamGrid.NZVertices - iBinZ);
1277 mDensity[side](iZ, iBinR - 1, iBinPhi - 1) = sum;
1278 }
1279 }
1280 }
1281}
1282
1283template <typename DataT>
1284template <typename ElectricFields>
1286{
1287 const Side side = formulaStruct.getSide();
1288 if (type == Type::Distortions) {
1289 initContainer(mLocalDistdR[side], true);
1290 initContainer(mLocalDistdZ[side], true);
1291 initContainer(mLocalDistdRPhi[side], true);
1292 } else {
1293 initContainer(mLocalCorrdR[side], true);
1294 initContainer(mLocalCorrdZ[side], true);
1295 initContainer(mLocalCorrdRPhi[side], true);
1296 }
1297
1298 // calculate local distortions/corrections for each vertex in the tpc
1299#pragma omp parallel for num_threads(sNThreads)
1300 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
1301 const DataT phi = getPhiVertex(iPhi, side);
1302 for (size_t iR = 0; iR < mParamGrid.NRVertices; ++iR) {
1303 const DataT radius = getRVertex(iR, side);
1304 for (size_t iZ = 0; iZ < mParamGrid.NZVertices - 1; ++iZ) {
1305 // set z coordinate depending on distortions or correction calculation
1306 const DataT z0 = type == Type::Corrections ? getZVertex(iZ + 1, side) : getZVertex(iZ, side);
1307 const DataT z1 = type == Type::Corrections ? getZVertex(iZ, side) : getZVertex(iZ + 1, side);
1308
1309 DataT drTmp = 0; // local distortion dR
1310 DataT dPhiTmp = 0; // local distortion dPhi (multiplication with R has to be done at the end)
1311 DataT dzTmp = 0; // local distortion dZ
1312
1313 const DataT stepSize = (z1 - z0) / sSteps; // the distortions are calculated by leting the elctron drift this distance in z direction
1314 for (int iter = 0; iter < sSteps; ++iter) {
1315 const DataT z0Tmp = (z0 + iter * stepSize + dzTmp); // starting z position
1316 const DataT z1Tmp = (z0Tmp + stepSize); // electron drifts from z0Tmp to z1Tmp
1317
1318 DataT ddR = 0; // distortion dR for drift from z0Tmp to z1Tmp
1319 DataT ddPhi = 0; // distortion dPhi for drift from z0Tmp to z1Tmp
1320 DataT ddZ = 0; // distortion dZ for drift from z0Tmp to z1Tmp
1321
1322 const DataT radiusTmp = regulateR(radius + drTmp, side); // current radial position
1323 const DataT phiTmp = regulatePhi(phi + dPhiTmp, side); // current phi position
1324
1325 // calculate distortions/corrections
1326 calcDistCorr(radiusTmp, phiTmp, z0Tmp, z1Tmp, ddR, ddPhi, ddZ, formulaStruct, true, side);
1327
1328 // add temp distortions to local distortions
1329 drTmp += ddR;
1330 dPhiTmp += ddPhi;
1331 dzTmp += ddZ;
1332 }
1333
1334 // store local distortions/corrections
1335 switch (type) {
1336 case Type::Corrections:
1337 mLocalCorrdR[side](iZ + 1, iR, iPhi) = drTmp;
1338 mLocalCorrdRPhi[side](iZ + 1, iR, iPhi) = dPhiTmp * radius;
1339 mLocalCorrdZ[side](iZ + 1, iR, iPhi) = dzTmp;
1340 break;
1341
1342 case Type::Distortions:
1343 mLocalDistdR[side](iZ, iR, iPhi) = drTmp;
1344 mLocalDistdRPhi[side](iZ, iR, iPhi) = dPhiTmp * radius;
1345 mLocalDistdZ[side](iZ, iR, iPhi) = dzTmp;
1346 break;
1347 }
1348 }
1349 // 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!
1350 switch (type) {
1351 case Type::Corrections:
1352 mLocalCorrdR[side](0, iR, iPhi) = 3 * (mLocalCorrdR[side](1, iR, iPhi) - mLocalCorrdR[side](2, iR, iPhi)) + mLocalCorrdR[side](3, iR, iPhi);
1353 mLocalCorrdRPhi[side](0, iR, iPhi) = 3 * (mLocalCorrdRPhi[side](1, iR, iPhi) - mLocalCorrdRPhi[side](2, iR, iPhi)) + mLocalCorrdRPhi[side](3, iR, iPhi);
1354 mLocalCorrdZ[side](0, iR, iPhi) = 3 * (mLocalCorrdZ[side](1, iR, iPhi) - mLocalCorrdZ[side](2, iR, iPhi)) + mLocalCorrdZ[side](3, iR, iPhi);
1355 break;
1356
1357 case Type::Distortions:
1358 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);
1359 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);
1360 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);
1361 break;
1362 }
1363 }
1364 }
1365}
1366
1367template <typename DataT>
1368template <typename ElectricFields>
1369void SpaceCharge<DataT>::calcLocalDistortionCorrectionVector(const ElectricFields& formulaStruct)
1370{
1371 const Side side = formulaStruct.getSide();
1372 initContainer(mLocalVecDistdR[side], true);
1373 initContainer(mLocalVecDistdZ[side], true);
1374 initContainer(mLocalVecDistdRPhi[side], true);
1375 initContainer(mElectricFieldEr[side], true);
1376 initContainer(mElectricFieldEz[side], true);
1377 initContainer(mElectricFieldEphi[side], true);
1378 // calculate local distortion/correction vector for each vertex in the tpc
1379#pragma omp parallel for num_threads(sNThreads)
1380 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
1381 for (size_t iR = 0; iR < mParamGrid.NRVertices; ++iR) {
1382 for (size_t iZ = 0; iZ < mParamGrid.NZVertices; ++iZ) {
1383 const DataT ezField = getEzField(formulaStruct.getSide());
1384 const DataT er = mElectricFieldEr[side](iZ, iR, iPhi);
1385 const DataT ez0 = mElectricFieldEz[side](iZ, iR, iPhi);
1386 const DataT ephi = mElectricFieldEphi[side](iZ, iR, iPhi);
1387 const DataT ez = getSign(formulaStruct.getSide()) * 1. / (ezField + ez0);
1388 const DataT erez = er * ez;
1389 const DataT ephiez = ephi * ez;
1390
1391 const DataT vecdR = mC0 * erez + mC1 * ephiez;
1392 const DataT vecdRPhi = mC0 * ephiez - mC1 * erez;
1393 const DataT vecdZ = -ez0 * TPCParameters<DataT>::DVDE;
1394
1395 mLocalVecDistdR[side](iZ, iR, iPhi) = vecdR;
1396 mLocalVecDistdRPhi[side](iZ, iR, iPhi) = vecdRPhi;
1397 mLocalVecDistdZ[side](iZ, iR, iPhi) = vecdZ;
1398 }
1399 }
1400 }
1401}
1402
1403template <typename DataT>
1404template <typename ElectricFields>
1406{
1407 if (type == Type::Distortions) {
1408 initContainer(mLocalDistdR[side], true);
1409 initContainer(mLocalDistdZ[side], true);
1410 initContainer(mLocalDistdRPhi[side], true);
1411 } else {
1412 initContainer(mLocalCorrdR[side], true);
1413 initContainer(mLocalCorrdZ[side], true);
1414 initContainer(mLocalCorrdRPhi[side], true);
1415 }
1416 // see: https://en.wikipedia.org/wiki/Runge%E2%80%93Kutta_methods
1417 // calculate local distortions/corrections for each vertex in the tpc using Runge Kutta 4 method
1418#pragma omp parallel for num_threads(sNThreads)
1419 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
1420 const DataT phi = getPhiVertex(iPhi, side);
1421 for (size_t iR = 0; iR < mParamGrid.NRVertices; ++iR) {
1422 const DataT radius = getRVertex(iR, side);
1423 for (size_t iZ = 0; iZ < mParamGrid.NZVertices - 1; ++iZ) {
1424 // set z coordinate depending on distortions or correction calculation
1425 const size_t iZ0 = type == Type::Corrections ? iZ + 1 : iZ;
1426 const size_t iZ1 = type == Type::Corrections ? iZ : iZ + 1;
1427
1428 const DataT z0 = getZVertex(iZ0, side);
1429 const DataT z1 = getZVertex(iZ1, side);
1430
1431 const DataT stepSize = z1 - z0; // h in the RK4 method
1432 const DataT absstepSize = std::abs(stepSize);
1433
1434 const DataT stepSizeHalf = 0.5 * stepSize; // half z bin for RK4
1435 const DataT absstepSizeHalf = std::abs(stepSizeHalf);
1436
1437 // starting position for RK4
1438 const DataT zk1 = z0;
1439 const DataT rk1 = radius;
1440 const DataT phik1 = phi;
1441
1442 DataT k1dR = 0; // derivative in r direction
1443 DataT k1dZ = 0; // derivative in z direction
1444 DataT k1dRPhi = 0; // derivative in rphi direction
1445
1446 // get derivative on current vertex
1447 switch (type) {
1448 case Type::Corrections:
1449 k1dR = getLocalVecCorrR(iZ0, iR, iPhi, side);
1450 k1dZ = getLocalVecCorrZ(iZ0, iR, iPhi, side);
1451 k1dRPhi = getLocalVecCorrRPhi(iZ0, iR, iPhi, side);
1452 break;
1453
1454 case Type::Distortions:
1455 k1dR = getLocalVecDistR(iZ0, iR, iPhi, side);
1456 k1dZ = getLocalVecDistZ(iZ0, iR, iPhi, side);
1457 k1dRPhi = getLocalVecDistRPhi(iZ0, iR, iPhi, side);
1458 break;
1459 }
1460
1461 // approximate position after half stepSize
1462 const DataT zk2 = zk1 + stepSizeHalf + absstepSizeHalf * k1dZ;
1463 const DataT rk2 = rk1 + absstepSizeHalf * k1dR;
1464 const DataT k1dPhi = k1dRPhi / rk1;
1465 const DataT phik2 = phik1 + absstepSizeHalf * k1dPhi;
1466
1467 // get derivative for new position
1468 DataT k2dR = 0;
1469 DataT k2dZ = 0;
1470 DataT k2dRPhi = 0;
1471 type == Type::Corrections ? getLocalCorrectionVectorCyl(zk2, rk2, phik2, side, k2dZ, k2dR, k2dRPhi) : getLocalDistortionVectorCyl(zk2, rk2, phik2, side, k2dZ, k2dR, k2dRPhi);
1472
1473 // approximate new position
1474 const DataT zk3 = zk1 + stepSizeHalf + absstepSizeHalf * k2dZ;
1475 const DataT rk3 = rk1 + absstepSizeHalf * k2dR;
1476 const DataT k2dPhi = k2dRPhi / rk2;
1477 const DataT phik3 = phik1 + absstepSizeHalf * k2dPhi;
1478
1479 DataT k3dR = 0;
1480 DataT k3dZ = 0;
1481 DataT k3dRPhi = 0;
1482 type == Type::Corrections ? getLocalCorrectionVectorCyl(zk3, rk3, phik3, side, k3dZ, k3dR, k3dRPhi) : getLocalDistortionVectorCyl(zk3, rk3, phik3, side, k3dZ, k3dR, k3dRPhi);
1483
1484 const DataT zk4 = zk1 + stepSize + absstepSize * k3dZ;
1485 const DataT rk4 = rk1 + absstepSize * k3dR;
1486 const DataT k3dPhi = k3dRPhi / rk3;
1487 const DataT phik4 = phik1 + absstepSize * k3dPhi;
1488
1489 DataT k4dR = 0;
1490 DataT k4dZ = 0;
1491 DataT k4dRPhi = 0;
1492 type == Type::Corrections ? getLocalCorrectionVectorCyl(zk4, rk4, phik4, side, k4dZ, k4dR, k4dRPhi) : getLocalDistortionVectorCyl(zk4, rk4, phik4, side, k4dZ, k4dR, k4dRPhi);
1493 const DataT k4dPhi = k4dRPhi / rk4;
1494
1495 // RK4 formula. See wikipedia: u = h * 1/6 * (k1 + 2*k2 + 2*k3 + k4)
1496 const DataT stepsizeSixth = absstepSize / 6;
1497 const DataT drRK = stepsizeSixth * (k1dR + 2 * k2dR + 2 * k3dR + k4dR);
1498 const DataT dzRK = stepsizeSixth * (k1dZ + 2 * k2dZ + 2 * k3dZ + k4dZ);
1499 const DataT dphiRK = stepsizeSixth * (k1dPhi + 2 * k2dPhi + 2 * k3dPhi + k4dPhi);
1500
1501 // store local distortions/corrections
1502 switch (type) {
1503 case Type::Corrections:
1504 mLocalCorrdR[side](iZ + 1, iR, iPhi) = drRK;
1505 mLocalCorrdRPhi[side](iZ + 1, iR, iPhi) = dphiRK * radius;
1506 mLocalCorrdZ[side](iZ + 1, iR, iPhi) = dzRK;
1507 break;
1508
1509 case Type::Distortions:
1510 mLocalDistdR[side](iZ, iR, iPhi) = drRK;
1511 mLocalDistdRPhi[side](iZ, iR, iPhi) = dphiRK * radius;
1512 mLocalDistdZ[side](iZ, iR, iPhi) = dzRK;
1513 break;
1514 }
1515 }
1516 // 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!
1517 switch (type) {
1518 case Type::Corrections:
1519 mLocalCorrdR[side](0, iR, iPhi) = 3 * (mLocalCorrdR[side](1, iR, iPhi) - mLocalCorrdR[side](2, iR, iPhi)) + mLocalCorrdR[side](3, iR, iPhi);
1520 mLocalCorrdRPhi[side](0, iR, iPhi) = 3 * (mLocalCorrdRPhi[side](1, iR, iPhi) - mLocalCorrdRPhi[side](2, iR, iPhi)) + mLocalCorrdRPhi[side](3, iR, iPhi);
1521 mLocalCorrdZ[side](0, iR, iPhi) = 3 * (mLocalCorrdZ[side](1, iR, iPhi) - mLocalCorrdZ[side](2, iR, iPhi)) + mLocalCorrdZ[side](3, iR, iPhi);
1522 break;
1523
1524 case Type::Distortions:
1525 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);
1526 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);
1527 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);
1528 break;
1529 }
1530 }
1531 }
1532}
1533
1534template <typename DataT>
1535template <typename Fields>
1536void SpaceCharge<DataT>::calcGlobalDistortions(const Fields& formulaStruct, const int maxIterations)
1537{
1538 const Side side = formulaStruct.getSide();
1539 initContainer(mGlobalDistdR[side], true);
1540 initContainer(mGlobalDistdZ[side], true);
1541 initContainer(mGlobalDistdRPhi[side], true);
1542 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
1543 // loop over tpc volume and let the electron drift from each vertex to the readout of the tpc
1544#pragma omp parallel for num_threads(sNThreads)
1545 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
1546 const DataT phi0 = getPhiVertex(iPhi, side);
1547 for (size_t iR = 0; iR < mParamGrid.NRVertices; ++iR) {
1548 const DataT r0 = getRVertex(iR, side);
1549 for (size_t iZ = 0; iZ < mParamGrid.NZVertices - 1; ++iZ) {
1550 const DataT z0 = getZVertex(iZ, side); // the electron starts at z0, r0, phi0
1551 DataT drDist = 0.0; // global distortion dR
1552 DataT dPhiDist = 0.0; // global distortion dPhi (multiplication with R has to be done at the end)
1553 DataT dzDist = 0.0; // global distortion dZ
1554 int iter = 0;
1555
1556 for (;;) {
1557 if (iter > maxIterations) {
1558 LOGP(error, "Aborting calculation of distortions for iZ: {}, iR: {}, iPhi: {} due to iteration '{}' > maxIterations '{}'!", iZ, iR, iPhi, iter, maxIterations);
1559 break;
1560 }
1561 const DataT z0Tmp = z0 + dzDist + iter * stepSize; // starting z position
1562
1563 // do not do check for first iteration
1564 if ((getSide(z0Tmp) != side) && iter) {
1565 LOGP(error, "Aborting calculation of distortions for iZ: {}, iR: {}, iPhi: {} due to change in the sides!", iZ, iR, iPhi);
1566 break;
1567 }
1568
1569 const DataT z1Tmp = z0Tmp + stepSize; // electron drifts from z0Tmp to z1Tmp
1570 const DataT radius = regulateR(r0 + drDist, side); // current radial position of the electron
1571 const DataT phi = regulatePhi(phi0 + dPhiDist, side); // current phi position of the electron
1572
1573 DataT ddR = 0; // distortion dR for drift from z0Tmp to z1Tmp
1574 DataT ddPhi = 0; // distortion dPhi for drift from z0Tmp to z1Tmp
1575 DataT ddZ = 0; // distortion dZ for drift from z0Tmp to z1Tmp
1576
1577 // get the distortion from interpolation of local distortions or calculate distortions with the electric field
1578 processGlobalDistCorr(radius, phi, z0Tmp, z1Tmp, ddR, ddPhi, ddZ, formulaStruct);
1579
1580 // if one uses local distortions the interpolated value for the last bin has to be scaled.
1581 // 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.
1582 const bool checkReached = side == Side::A ? z1Tmp >= getZMax(side) : z1Tmp <= getZMax(side);
1583 if (formulaStruct.getID() == 2 && checkReached) {
1584 const DataT fac = std::abs((getZMax(side) - z0Tmp) * getInvSpacingZ(side));
1585 ddR *= fac;
1586 ddZ *= fac;
1587 ddPhi *= fac;
1588 }
1589
1590 // add local distortions to global distortions
1591 drDist += ddR;
1592 dPhiDist += ddPhi;
1593 dzDist += ddZ;
1594
1595 // 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.
1596 // approximation is done by the current calculated values of the distortions and scaled linear to the 'missing' distance.
1597 if (checkReached) {
1598 const DataT endPoint = z1Tmp + ddZ;
1599 const DataT deltaZ = getZMax(side) - endPoint; // distance from last point to read out
1600 const DataT diff = endPoint - z0Tmp;
1601 const DataT fac = diff != 0 ? std::abs(deltaZ / diff) : 0; // approximate the distortions for the 'missing' distance deltaZ
1602 drDist += ddR * fac;
1603 dPhiDist += ddPhi * fac;
1604 dzDist += ddZ * fac;
1605 break;
1606 }
1607 ++iter;
1608 }
1609 // store global distortions
1610 mGlobalDistdR[side](iZ, iR, iPhi) = drDist;
1611 mGlobalDistdRPhi[side](iZ, iR, iPhi) = dPhiDist * r0;
1612 mGlobalDistdZ[side](iZ, iR, iPhi) = dzDist;
1613 }
1614 }
1615 }
1616}
1617
1618template <typename DataT>
1619template <typename Formulas>
1620void SpaceCharge<DataT>::calcGlobalCorrections(const Formulas& formulaStruct, const int type)
1621{
1622 using timer = std::chrono::high_resolution_clock;
1623 auto start = timer::now();
1624 const Side side = formulaStruct.getSide();
1625 initContainer(mGlobalCorrdR[side], true);
1626 initContainer(mGlobalCorrdZ[side], true);
1627 initContainer(mGlobalCorrdRPhi[side], true);
1628
1629 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
1630 const DataT stepSize = -getGridSpacingZ(side) / iSteps;
1631// loop over tpc volume and let the electron drift from each vertex to the readout of the tpc
1632#pragma omp parallel for num_threads(sNThreads)
1633 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
1634 const DataT phi0 = getPhiVertex(iPhi, side);
1635 for (size_t iR = 0; iR < mParamGrid.NRVertices; ++iR) {
1636
1637 const DataT r0 = getRVertex(iR, side);
1638 DataT drCorr = 0;
1639 DataT dPhiCorr = 0;
1640 DataT dzCorr = 0;
1641 bool isOutOfVolume = false;
1642
1643 // start at the readout and follow electron towards central electrode
1644 for (size_t iZ = mParamGrid.NZVertices - 1; iZ >= 1; --iZ) {
1645 const DataT z0 = getZVertex(iZ, side); // the electron starts at z0, r0, phi0
1646 // 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.
1647 bool centralElectrodeReached = false;
1648 for (int iter = 0; iter < iSteps; ++iter) {
1649 if ((type != 3) && (centralElectrodeReached || isOutOfVolume)) {
1650 break;
1651 }
1652 DataT radius = r0 + drCorr; // current radial position of the electron
1653 DataT phi = phi0 + dPhiCorr; // current phi position of the electron
1654 const DataT z0Tmp = z0 + dzCorr + iter * stepSize; // starting z position
1655 DataT z1Tmp = z0Tmp + stepSize; // follow electron from z0Tmp to z1Tmp
1656
1657 // restrict to inner TPC volume
1658 if (type != 3) {
1659 radius = regulateR(radius, side);
1660 phi = regulatePhi(phi, side);
1661 z1Tmp = regulateZ(z1Tmp, side);
1662 }
1663
1664 DataT ddR = 0; // distortion dR for z0Tmp to z1Tmp
1665 DataT ddPhi = 0; // distortion dPhi for z0Tmp to z1Tmp
1666 DataT ddZ = 0; // distortion dZ for z0Tmp to z1Tmp
1667
1668 // get the distortion from interpolation of local distortions or calculate distortions with the electric field
1669 processGlobalDistCorr(radius, phi, z0Tmp, z1Tmp, ddR, ddPhi, ddZ, formulaStruct);
1670
1671 // if one uses local corrections the interpolated value for the first bin has to be scaled.
1672 // 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.
1673 centralElectrodeReached = getSign(side) * z1Tmp <= getZMin(side);
1674 if (formulaStruct.getID() == 2 && centralElectrodeReached) {
1675 const DataT fac = (z0Tmp - getZMin(side)) * getInvSpacingZ(side);
1676 ddR *= fac;
1677 ddZ *= fac;
1678 ddPhi *= fac;
1679 }
1680
1681 // calculate current r and z position after correction
1682 const DataT rCurr = r0 + drCorr + ddR;
1683 const DataT zCurr = z0Tmp + dzCorr + ddZ + stepSize;
1684
1685 // 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
1686 if ((type != 3) && (rCurr <= getRMinSim(side) || rCurr >= getRMaxSim(side) || (std::abs(zCurr) > 1.2 * std::abs(getZMax(side))))) {
1687 isOutOfVolume = true;
1688 break;
1689 }
1690
1691 // add local corrections to global corrections
1692 drCorr += ddR;
1693 dPhiCorr += ddPhi;
1694 dzCorr += ddZ;
1695
1696 // 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.
1697 // approximation is done by the current calculated values of the corrections and scaled linear to the 'missing' distance deltaZ. (NOT TESTED)
1698 if ((type != 3) && centralElectrodeReached) {
1699 const DataT endPoint = z1Tmp + ddZ;
1700 const DataT deltaZ = endPoint - getZMin(side);
1701 const DataT diff = z0Tmp - endPoint;
1702 const DataT fac = diff != 0 ? deltaZ / diff : 0; // approximate the distortions for the 'missing' distance deltaZ
1703 drCorr += ddR * fac;
1704 dPhiCorr += ddPhi * fac;
1705 dzCorr += ddZ * fac;
1706 break;
1707 }
1708 }
1709 // store global corrections
1710 if ((type == 1 || type == 2) && (centralElectrodeReached || isOutOfVolume)) {
1711 mGlobalCorrdR[side](iZ - 1, iR, iPhi) = -1;
1712 mGlobalCorrdRPhi[side](iZ - 1, iR, iPhi) = -1;
1713 mGlobalCorrdZ[side](iZ - 1, iR, iPhi) = -1;
1714 } else {
1715 mGlobalCorrdR[side](iZ - 1, iR, iPhi) = drCorr;
1716 mGlobalCorrdRPhi[side](iZ - 1, iR, iPhi) = dPhiCorr * r0;
1717 mGlobalCorrdZ[side](iZ - 1, iR, iPhi) = dzCorr;
1718 }
1719 }
1720 }
1721
1722 if (type != 0) {
1723 // fill / extrapolate out of volume values along r
1724 for (int iZ = mParamGrid.NZVertices - 1; iZ >= 0; --iZ) {
1725 // from middle of the radius to IFC
1726 for (int iR = (mParamGrid.NRVertices / 2); iR >= 0; --iR) {
1727 if ((mGlobalCorrdR[side](iZ, iR, iPhi) == -1) && (mGlobalCorrdRPhi[side](iZ, iR, iPhi) == -1) && (mGlobalCorrdZ[side](iZ, iR, iPhi) == -1)) {
1728 const size_t iRUp = iR + 1;
1729 if (type == 1) {
1730 // just replace with last valid number (assumption: values at iR==mParamGrid.NRVertices / 2 are valid)
1731 mGlobalCorrdR[side](iZ, iR, iPhi) = mGlobalCorrdR[side](iZ, iRUp, iPhi);
1732 mGlobalCorrdRPhi[side](iZ, iR, iPhi) = mGlobalCorrdR[side](iZ, iRUp, iPhi);
1733 mGlobalCorrdZ[side](iZ, iR, iPhi) = mGlobalCorrdR[side](iZ, iRUp, iPhi);
1734 } else if (type == 2) {
1735 // extrapolate values
1736 const size_t iRUpTwo = iR + 2;
1737 const size_t iRUpThree = iR + 3;
1738 mGlobalCorrdR[side](iZ, iR, iPhi) = 3 * (mGlobalCorrdR[side](iZ, iRUp, iPhi) - mGlobalCorrdR[side](iZ, iRUpTwo, iPhi)) + mGlobalCorrdR[side](iZ, iRUpThree, iPhi);
1739 mGlobalCorrdRPhi[side](iZ, iR, iPhi) = 3 * (mGlobalCorrdRPhi[side](iZ, iRUp, iPhi) - mGlobalCorrdRPhi[side](iZ, iRUpTwo, iPhi)) + mGlobalCorrdRPhi[side](iZ, iRUpThree, iPhi);
1740 mGlobalCorrdZ[side](iZ, iR, iPhi) = 3 * (mGlobalCorrdZ[side](iZ, iRUp, iPhi) - mGlobalCorrdZ[side](iZ, iRUpTwo, iPhi)) + mGlobalCorrdZ[side](iZ, iRUpThree, iPhi);
1741 }
1742 }
1743 }
1744 // from middle of the radius to OFC
1745 for (int iR = (mParamGrid.NRVertices / 2); iR < mParamGrid.NRVertices; ++iR) {
1746 if ((mGlobalCorrdR[side](iZ, iR, iPhi) == -1) && (mGlobalCorrdRPhi[side](iZ, iR, iPhi) == -1) && (mGlobalCorrdZ[side](iZ, iR, iPhi) == -1)) {
1747 const size_t iRUp = iR - 1;
1748 if (type == 1) {
1749 // just replace with last valid number (assumption: values at iR==mParamGrid.NRVertices / 2 are valid)
1750 mGlobalCorrdR[side](iZ, iR, iPhi) = mGlobalCorrdR[side](iZ, iRUp, iPhi);
1751 mGlobalCorrdRPhi[side](iZ, iR, iPhi) = mGlobalCorrdR[side](iZ, iRUp, iPhi);
1752 mGlobalCorrdZ[side](iZ, iR, iPhi) = mGlobalCorrdR[side](iZ, iRUp, iPhi);
1753 } else if (type == 2) {
1754 // extrapolate values
1755 const size_t iRUpTwo = iR - 2;
1756 const size_t iRUpThree = iR - 3;
1757 mGlobalCorrdR[side](iZ, iR, iPhi) = 3 * (mGlobalCorrdR[side](iZ, iRUp, iPhi) - mGlobalCorrdR[side](iZ, iRUpTwo, iPhi)) + mGlobalCorrdR[side](iZ, iRUpThree, iPhi);
1758 mGlobalCorrdRPhi[side](iZ, iR, iPhi) = 3 * (mGlobalCorrdRPhi[side](iZ, iRUp, iPhi) - mGlobalCorrdRPhi[side](iZ, iRUpTwo, iPhi)) + mGlobalCorrdRPhi[side](iZ, iRUpThree, iPhi);
1759 mGlobalCorrdZ[side](iZ, iR, iPhi) = 3 * (mGlobalCorrdZ[side](iZ, iRUp, iPhi) - mGlobalCorrdZ[side](iZ, iRUpTwo, iPhi)) + mGlobalCorrdZ[side](iZ, iRUpThree, iPhi);
1760 }
1761 }
1762 }
1763 }
1764 }
1765 }
1766 // set flag that global corrections are set to true
1767 auto stop = timer::now();
1768 std::chrono::duration<float> time = stop - start;
1769 const float totalTime = time.count();
1770 LOGP(detail, "calcGlobalCorrections took {}s", totalTime);
1771}
1772
1773template <typename DataT>
1775{
1776 DataT corrX{};
1777 DataT corrY{};
1778 DataT corrZ{};
1779 const Side side = getSide(point.Z());
1780
1781 // get the distortions for input coordinate
1782 getCorrections(point.X(), point.Y(), point.Z(), side, corrX, corrY, corrZ);
1783
1784 // set distorted coordinates
1785 point.SetXYZ(point.X() + corrX, point.Y() + corrY, point.Y() + corrY);
1786}
1787
1788template <typename DataT>
1789void SpaceCharge<DataT>::distortElectron(GlobalPosition3D& point, const SpaceCharge<DataT>* scSCale, float scale) const
1790{
1791 DataT distX{};
1792 DataT distY{};
1793 DataT distZ{};
1794 const Side side = getSide(point.Z());
1795 // get the distortions for input coordinate
1796 getDistortions(point.X(), point.Y(), point.Z(), side, distX, distY, distZ);
1797
1798 DataT distXTmp{};
1799 DataT distYTmp{};
1800 DataT distZTmp{};
1801
1802 // scale distortions if requested
1803 if (scSCale && scale != 0) {
1804 scSCale->getDistortions(point.X() + distX, point.Y() + distY, point.Z() + distZ, side, distXTmp, distYTmp, distZTmp);
1805 distX += distXTmp * scale;
1806 distY += distYTmp * scale;
1807 distZ += distZTmp * scale;
1808 }
1809
1810 GPUCA_DEBUG_STREAMER_CHECK(if (o2::utils::DebugStreamer::checkStream(o2::utils::StreamFlags::streamDistortionsSC)) {
1811 GlobalPosition3D pos(point);
1812 float phi = std::atan2(pos.Y(), pos.X());
1813 if (phi < 0.) {
1814 phi += TWOPI;
1815 }
1816 unsigned char secNum = std::floor(phi / SECPHIWIDTH);
1817 const Sector sector(secNum + (pos.Z() < 0) * SECTORSPERSIDE);
1819
1820 o2::utils::DebugStreamer::instance()->getStreamer("debug_distortElectron", "UPDATE") << o2::utils::DebugStreamer::instance()->getUniqueTreeName("debug_distortElectron").data()
1821 << "pos=" << pos
1822 << "lPos=" << lPos
1823 << "phi=" << phi
1824 << "secNum=" << secNum
1825 << "distX=" << distX
1826 << "distY=" << distY
1827 << "distZ=" << distZ
1828 << "distXDer=" << distXTmp
1829 << "distYDer=" << distYTmp
1830 << "distZDer=" << distZTmp
1831 << "scale=" << scale
1832 << "\n";
1833 })
1834
1835 // set distorted coordinates
1836 point.SetXYZ(point.X() + distX, point.Y() + distY, point.Z() + distZ);
1837}
1838
1839template <typename DataT>
1840DataT SpaceCharge<DataT>::getDensityCyl(const DataT z, const DataT r, const DataT phi, const Side side) const
1841{
1842 return mInterpolatorDensity[side](z, r, phi);
1843}
1844
1845template <typename DataT>
1846std::vector<float> SpaceCharge<DataT>::getDensityCyl(const std::vector<DataT>& z, const std::vector<DataT>& r, const std::vector<DataT>& phi, const Side side) const
1847{
1848 const auto nPoints = z.size();
1849 std::vector<float> density(nPoints);
1850#pragma omp parallel for num_threads(sNThreads)
1851 for (size_t i = 0; i < nPoints; ++i) {
1852 density[i] = getDensityCyl(z[i], r[i], phi[i], side);
1853 }
1854 return density;
1855}
1856
1857template <typename DataT>
1859{
1860 return mInterpolatorPotential[side](z, r, phi);
1861}
1862
1863template <typename DataT>
1864std::vector<float> SpaceCharge<DataT>::getPotentialCyl(const std::vector<DataT>& z, const std::vector<DataT>& r, const std::vector<DataT>& phi, const Side side) const
1865{
1866 const auto nPoints = z.size();
1867 std::vector<float> potential(nPoints);
1868#pragma omp parallel for num_threads(sNThreads)
1869 for (size_t i = 0; i < nPoints; ++i) {
1870 potential[i] = getPotentialCyl(z[i], r[i], phi[i], side);
1871 }
1872 return potential;
1873}
1874
1875template <typename DataT>
1876void SpaceCharge<DataT>::getElectricFieldsCyl(const DataT z, const DataT r, const DataT phi, const Side side, DataT& eZ, DataT& eR, DataT& ePhi) const
1877{
1878 eZ = mInterpolatorEField[side].evalFieldZ(z, r, phi);
1879 eR = mInterpolatorEField[side].evalFieldR(z, r, phi);
1880 ePhi = mInterpolatorEField[side].evalFieldPhi(z, r, phi);
1881}
1882
1883template <typename DataT>
1884void SpaceCharge<DataT>::getLocalCorrectionsCyl(const DataT z, const DataT r, const DataT phi, const Side side, DataT& lcorrZ, DataT& lcorrR, DataT& lcorrRPhi) const
1885{
1886 lcorrZ = mInterpolatorLocalCorr[side].evaldZ(z, r, phi);
1887 lcorrR = mInterpolatorLocalCorr[side].evaldR(z, r, phi);
1888 lcorrRPhi = mInterpolatorLocalCorr[side].evaldRPhi(z, r, phi);
1889}
1890
1891template <typename DataT>
1892void 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
1893{
1894 const auto nPoints = z.size();
1895 lcorrZ.resize(nPoints);
1896 lcorrR.resize(nPoints);
1897 lcorrRPhi.resize(nPoints);
1898#pragma omp parallel for num_threads(sNThreads)
1899 for (size_t i = 0; i < nPoints; ++i) {
1900 getLocalCorrectionsCyl(z[i], r[i], phi[i], side, lcorrZ[i], lcorrR[i], lcorrRPhi[i]);
1901 }
1902}
1903
1904template <typename DataT>
1905void SpaceCharge<DataT>::getCorrectionsCyl(const DataT z, const DataT r, const DataT phi, const Side side, DataT& corrZ, DataT& corrR, DataT& corrRPhi) const
1906{
1907 corrZ = mInterpolatorGlobalCorr[side].evaldZ(z, r, phi);
1908 corrR = mInterpolatorGlobalCorr[side].evaldR(z, r, phi);
1909 corrRPhi = mInterpolatorGlobalCorr[side].evaldRPhi(z, r, phi);
1910}
1911
1912template <typename DataT>
1913void 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
1914{
1915 const auto nPoints = z.size();
1916 corrZ.resize(nPoints);
1917 corrR.resize(nPoints);
1918 corrRPhi.resize(nPoints);
1919#pragma omp parallel for num_threads(sNThreads)
1920 for (size_t i = 0; i < nPoints; ++i) {
1921 getCorrectionsCyl(z[i], r[i], phi[i], side, corrZ[i], corrR[i], corrRPhi[i]);
1922 }
1923}
1924
1925template <typename DataT>
1926void SpaceCharge<DataT>::getCorrections(const DataT x, const DataT y, const DataT z, const Side side, DataT& corrX, DataT& corrY, DataT& corrZ) const
1927{
1928 if (mUseAnaDistCorr) {
1929 getCorrectionsAnalytical(x, y, z, side, corrX, corrY, corrZ);
1930 } else {
1931 // convert cartesian to polar
1932 const DataT radius = getRadiusFromCartesian(x, y);
1933 DataT phi = getPhiFromCartesian(x, y);
1935
1936 DataT corrR{};
1937 DataT corrRPhi{};
1938 getCorrectionsCyl(z, radius, phi, side, corrZ, corrR, corrRPhi);
1939
1940 // Calculate corrected position
1941 const DataT radiusCorr = radius + corrR;
1942 const DataT phiCorr = phi + corrRPhi / radius;
1943
1944 corrX = getXFromPolar(radiusCorr, phiCorr) - x; // difference between corrected and original x coordinate
1945 corrY = getYFromPolar(radiusCorr, phiCorr) - y; // difference between corrected and original y coordinate
1946 }
1947}
1948
1949template <typename DataT>
1950void SpaceCharge<DataT>::getLocalDistortionsCyl(const DataT z, const DataT r, const DataT phi, const Side side, DataT& ldistZ, DataT& ldistR, DataT& ldistRPhi) const
1951{
1952 ldistZ = mInterpolatorLocalDist[side].evaldZ(z, r, phi);
1953 ldistR = mInterpolatorLocalDist[side].evaldR(z, r, phi);
1954 ldistRPhi = mInterpolatorLocalDist[side].evaldRPhi(z, r, phi);
1955}
1956
1957template <typename DataT>
1958void 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
1959{
1960 const auto nPoints = z.size();
1961 ldistZ.resize(nPoints);
1962 ldistR.resize(nPoints);
1963 ldistRPhi.resize(nPoints);
1964#pragma omp parallel for num_threads(sNThreads)
1965 for (size_t i = 0; i < nPoints; ++i) {
1966 getLocalDistortionsCyl(z[i], r[i], phi[i], side, ldistZ[i], ldistR[i], ldistRPhi[i]);
1967 }
1968}
1969
1970template <typename DataT>
1971void SpaceCharge<DataT>::getLocalDistortionVectorCyl(const DataT z, const DataT r, const DataT phi, const Side side, DataT& lvecdistZ, DataT& lvecdistR, DataT& lvecdistRPhi) const
1972{
1973 lvecdistZ = mInterpolatorLocalVecDist[side].evaldZ(z, r, phi);
1974 lvecdistR = mInterpolatorLocalVecDist[side].evaldR(z, r, phi);
1975 lvecdistRPhi = mInterpolatorLocalVecDist[side].evaldRPhi(z, r, phi);
1976}
1977
1978template <typename DataT>
1979void 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
1980{
1981 const auto nPoints = z.size();
1982 lvecdistZ.resize(nPoints);
1983 lvecdistR.resize(nPoints);
1984 lvecdistRPhi.resize(nPoints);
1985#pragma omp parallel for num_threads(sNThreads)
1986 for (size_t i = 0; i < nPoints; ++i) {
1987 getLocalDistortionVectorCyl(z[i], r[i], phi[i], side, lvecdistZ[i], lvecdistR[i], lvecdistRPhi[i]);
1988 }
1989}
1990
1991template <typename DataT>
1992void SpaceCharge<DataT>::getLocalCorrectionVectorCyl(const DataT z, const DataT r, const DataT phi, const Side side, DataT& lveccorrZ, DataT& lveccorrR, DataT& lveccorrRPhi) const
1993{
1994 lveccorrZ = -mInterpolatorLocalVecDist[side].evaldZ(z, r, phi);
1995 lveccorrR = -mInterpolatorLocalVecDist[side].evaldR(z, r, phi);
1996 lveccorrRPhi = -mInterpolatorLocalVecDist[side].evaldRPhi(z, r, phi);
1997}
1998
1999template <typename DataT>
2000void 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
2001{
2002 const auto nPoints = z.size();
2003 lveccorrZ.resize(nPoints);
2004 lveccorrR.resize(nPoints);
2005 lveccorrRPhi.resize(nPoints);
2006#pragma omp parallel for num_threads(sNThreads)
2007 for (size_t i = 0; i < nPoints; ++i) {
2008 getLocalCorrectionVectorCyl(z[i], r[i], phi[i], side, lveccorrZ[i], lveccorrR[i], lveccorrRPhi[i]);
2009 }
2010}
2011
2012template <typename DataT>
2013void SpaceCharge<DataT>::getDistortionsCyl(const DataT z, const DataT r, const DataT phi, const Side side, DataT& distZ, DataT& distR, DataT& distRPhi) const
2014{
2015 distZ = mInterpolatorGlobalDist[side].evaldZ(z, r, phi);
2016 distR = mInterpolatorGlobalDist[side].evaldR(z, r, phi);
2017 distRPhi = mInterpolatorGlobalDist[side].evaldRPhi(z, r, phi);
2018}
2019
2020template <typename DataT>
2021void 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
2022{
2023 const auto nPoints = z.size();
2024 distZ.resize(nPoints);
2025 distR.resize(nPoints);
2026 distRPhi.resize(nPoints);
2027#pragma omp parallel for num_threads(sNThreads)
2028 for (size_t i = 0; i < nPoints; ++i) {
2029 getDistortionsCyl(z[i], r[i], phi[i], side, distZ[i], distR[i], distRPhi[i]);
2030 }
2031}
2032
2033template <typename DataT>
2034void SpaceCharge<DataT>::getDistortions(const DataT x, const DataT y, const DataT z, const Side side, DataT& distX, DataT& distY, DataT& distZ) const
2035{
2036 DataT zClamped = regulateZ(z, side);
2037
2038 if (mUseAnaDistCorr) {
2039 getDistortionsAnalytical(x, y, zClamped, side, distX, distY, distZ);
2040 } else {
2041 // convert cartesian to polar
2042 const DataT radius = getRadiusFromCartesian(x, y);
2043 const DataT phi = getPhiFromCartesian(x, y);
2044
2045 DataT distR{};
2046 DataT distRPhi{};
2047 DataT rClamped = regulateR(radius, side);
2048 getDistortionsCyl(zClamped, rClamped, phi, side, distZ, distR, distRPhi);
2049
2050 // Calculate distorted position
2051 const DataT radiusDist = rClamped + distR;
2052 const DataT phiDist = phi + distRPhi / rClamped;
2053
2054 distX = getXFromPolar(radiusDist, phiDist) - x; // difference between distorted and original x coordinate
2055 distY = getYFromPolar(radiusDist, phiDist) - y; // difference between distorted and original y coordinate
2056 }
2057}
2058
2059template <typename DataT>
2060void 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
2061{
2062 const GlobalPosition3D pos(x, y, z);
2063 float phi = std::atan2(pos.Y(), pos.X());
2064 if (phi < 0.) {
2065 phi += TWOPI;
2066 }
2067 const unsigned char secNum = std::floor(phi / SECPHIWIDTH);
2068 const Sector sector(secNum + (pos.Z() < 0) * SECTORSPERSIDE);
2069 const LocalPosition3D lPos = Mapper::GlobalToLocal(pos, sector);
2070
2071 // convert dlx and dlY to r, rPhi
2072 const DataT dlX = dist ? mAnaDistCorr.getDistortionsLX(lPos.X(), lPos.Y(), lPos.Z(), side) : mAnaDistCorr.getCorrectionsLX(lPos.X(), lPos.Y(), lPos.Z(), side);
2073 const DataT dlY = dist ? mAnaDistCorr.getDistortionsLY(lPos.X(), lPos.Y(), lPos.Z(), side) : mAnaDistCorr.getCorrectionsLY(lPos.X(), lPos.Y(), lPos.Z(), side);
2074 const DataT dlZ = dist ? mAnaDistCorr.getDistortionsLZ(lPos.X(), lPos.Y(), lPos.Z(), side) : mAnaDistCorr.getCorrectionsLZ(lPos.X(), lPos.Y(), lPos.Z(), side);
2075
2076 // convert distortios in local coordinates to global coordinates
2077 // distorted local position
2078 const LocalPosition3D lPosDist(lPos.X() + dlX, lPos.Y() + dlY, lPos.Z() + dlZ);
2079
2080 // calc global position
2081 const auto globalPosDist = Mapper::LocalToGlobal(lPosDist, sector);
2082 distX = globalPosDist.X() - x;
2083 distY = globalPosDist.Y() - y;
2084 distZ = globalPosDist.Z() - z;
2085
2086 GPUCA_DEBUG_STREAMER_CHECK(if (o2::utils::DebugStreamer::checkStream(o2::utils::StreamFlags::streamDistortionsSC)) {
2087 o2::utils::DebugStreamer::instance()->getStreamer("debug_distortions_analytical", "UPDATE") << o2::utils::DebugStreamer::instance()->getUniqueTreeName("debug_distortions_analytical").data()
2088 << "pos=" << (*const_cast<GlobalPosition3D*>(&pos))
2089 << "lPos=" << (*const_cast<LocalPosition3D*>(&lPos))
2090 << "dlX=" << (*const_cast<DataT*>(&dlX))
2091 << "dlY=" << (*const_cast<DataT*>(&dlY))
2092 << "dlZ=" << (*const_cast<DataT*>(&dlZ))
2093 << "distX=" << distX
2094 << "distY=" << distY
2095 << "distZ=" << distZ
2096 << "\n";
2097 })
2098}
2099
2100template <typename DataT>
2102{
2103 using timer = std::chrono::high_resolution_clock;
2104 if (!mInitLookUpTables) {
2105 auto start = timer::now();
2106 auto o2field = static_cast<o2::field::MagneticField*>(TGeoGlobalMagField::Instance()->GetField());
2107 const float bzField = o2field->solenoidField(); // magnetic field in kGauss
2109 auto& gasParam = ParameterGas::Instance();
2110 float vDrift = gasParam.DriftV; // drift velocity in cm/us
2112 const float t1 = 1.;
2113 const float t2 = 1.;
2115 const float omegaTau = -10. * bzField * vDrift / std::abs(getEzField(Side::A));
2116 setOmegaTauT1T2(omegaTau, t1, t2);
2117 if (mUseInitialSCDensity) {
2118 LOG(warning) << "mUseInitialSCDensity" << mUseInitialSCDensity;
2119 calculateDistortionsCorrections(Side::A);
2120 calculateDistortionsCorrections(Side::C);
2121 mInitLookUpTables = true;
2122 }
2123 auto stop = timer::now();
2124 std::chrono::duration<float> time = stop - start;
2125 LOGP(info, "Total Time Distortions and Corrections for A and C Side: {}", time.count());
2126 }
2127}
2128
2129template <typename DataT>
2130void SpaceCharge<DataT>::setDistortionLookupTables(const DataContainer& distdZ, const DataContainer& distdR, const DataContainer& distdRPhi, const Side side)
2131{
2132 mGlobalDistdR[side] = distdR;
2133 mGlobalDistdZ[side] = distdZ;
2134 mGlobalDistdRPhi[side] = distdRPhi;
2135}
2136
2137template <typename DataT>
2138template <typename Fields>
2139void 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
2140{
2141 TF1 fErOverEz(
2142 "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);
2143 localIntErOverEz = static_cast<DataT>(fErOverEz.Integral(p1z, p2z));
2144
2145 TF1 fEphiOverEz(
2146 "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);
2147 localIntEPhiOverEz = static_cast<DataT>(fEphiOverEz.Integral(p1z, p2z));
2148
2149 TF1 fEz(
2150 "fEZOverEz", [&](double* x, double* p) { (void)p; return static_cast<double>(formulaStruct.evalFieldZ(static_cast<DataT>(x[0]), p1r, p1phi) - ezField); }, p1z, p2z, 1);
2151 localIntDeltaEz = getSign(side) * static_cast<DataT>(fEz.Integral(p1z, p2z));
2152}
2153
2154template <typename DataT>
2155template <typename Fields>
2156void 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
2157{
2158 //========trapezoidal rule see: https://en.wikipedia.org/wiki/Trapezoidal_rule ==============
2159 const DataT fielder0 = formulaStruct.evalFieldR(p1z, p1r, p1phi);
2160 const DataT fieldez0 = formulaStruct.evalFieldZ(p1z, p1r, p1phi);
2161 const DataT fieldephi0 = formulaStruct.evalFieldPhi(p1z, p1r, p1phi);
2162
2163 const DataT fielder1 = formulaStruct.evalFieldR(p2z, p1r, p1phi);
2164 const DataT fieldez1 = formulaStruct.evalFieldZ(p2z, p1r, p1phi);
2165 const DataT fieldephi1 = formulaStruct.evalFieldPhi(p2z, p1r, p1phi);
2166
2167 const DataT eZ0 = isCloseToZero(ezField, fieldez0) ? 0 : 1. / (ezField + fieldez0);
2168 const DataT eZ1 = isCloseToZero(ezField, fieldez1) ? 0 : 1. / (ezField + fieldez1);
2169
2170 const DataT deltaX = 0.5 * (p2z - p1z);
2171 localIntErOverEz = deltaX * (fielder0 * eZ0 + fielder1 * eZ1);
2172 localIntEPhiOverEz = deltaX * (fieldephi0 * eZ0 + fieldephi1 * eZ1);
2173 localIntDeltaEz = getSign(side) * deltaX * (fieldez0 + fieldez1);
2174}
2175
2176template <typename DataT>
2177template <typename Fields>
2178void 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
2179{
2180 //==========simpsons rule see: https://en.wikipedia.org/wiki/Simpson%27s_rule =============================
2181 const DataT fielder0 = formulaStruct.evalFieldR(p1z, p1r, p1phi);
2182 const DataT fieldez0 = formulaStruct.evalFieldZ(p1z, p1r, p1phi);
2183 const DataT fieldephi0 = formulaStruct.evalFieldPhi(p1z, p1r, p1phi);
2184
2185 const DataT fielder1 = formulaStruct.evalFieldR(p2z, p1r, p1phi);
2186 const DataT fieldez1 = formulaStruct.evalFieldZ(p2z, p1r, p1phi);
2187 const DataT fieldephi1 = formulaStruct.evalFieldPhi(p2z, p1r, p1phi);
2188
2189 const DataT deltaX = p2z - p1z;
2190 const DataT xk2N = (p2z - static_cast<DataT>(0.5) * deltaX);
2191 const DataT ezField2 = formulaStruct.evalFieldZ(xk2N, p1r, p1phi);
2192 const DataT ezField2Denominator = isCloseToZero(ezField, ezField2) ? 0 : 1. / (ezField + ezField2);
2193 const DataT fieldSum2ErOverEz = formulaStruct.evalFieldR(xk2N, p1r, p1phi) * ezField2Denominator;
2194 const DataT fieldSum2EphiOverEz = formulaStruct.evalFieldPhi(xk2N, p1r, p1phi) * ezField2Denominator;
2195
2196 const DataT eZ0 = isCloseToZero(ezField, fieldez0) ? 0 : 1. / (ezField + fieldez0);
2197 const DataT eZ1 = isCloseToZero(ezField, fieldez1) ? 0 : 1. / (ezField + fieldez1);
2198
2199 const DataT deltaXSimpsonSixth = deltaX / 6.;
2200 localIntErOverEz = deltaXSimpsonSixth * (4. * fieldSum2ErOverEz + fielder0 * eZ0 + fielder1 * eZ1);
2201 localIntEPhiOverEz = deltaXSimpsonSixth * (4. * fieldSum2EphiOverEz + fieldephi0 * eZ0 + fieldephi1 * eZ1);
2202 localIntDeltaEz = getSign(side) * deltaXSimpsonSixth * (4. * ezField2 + fieldez0 + fieldez1);
2203}
2204
2205template <typename DataT>
2206template <typename Fields>
2207void 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
2208{
2209 //==========simpsons rule see: https://en.wikipedia.org/wiki/Simpson%27s_rule =============================
2210 // const Side side = formulaStruct.getSide();
2211 // const DataT ezField = getEzField(side);
2212 const DataT p2phiSave = regulatePhi(p2phi, side);
2213
2214 const DataT fielder0 = formulaStruct.evalFieldR(p1z, p1r, p1phi);
2215 const DataT fieldez0 = formulaStruct.evalFieldZ(p1z, p1r, p1phi);
2216 const DataT fieldephi0 = formulaStruct.evalFieldPhi(p1z, p1r, p1phi);
2217
2218 const DataT fielder1 = formulaStruct.evalFieldR(p2z, p2r, p2phiSave);
2219 const DataT fieldez1 = formulaStruct.evalFieldZ(p2z, p2r, p2phiSave);
2220 const DataT fieldephi1 = formulaStruct.evalFieldPhi(p2z, p2r, p2phiSave);
2221
2222 const DataT eZ0Inv = isCloseToZero(ezField, fieldez0) ? 0 : 1. / (ezField + fieldez0);
2223 const DataT eZ1Inv = isCloseToZero(ezField, fieldez1) ? 0 : 1. / (ezField + fieldez1);
2224
2225 const DataT pHalfZ = 0.5 * (p1z + p2z); // dont needs to be regulated since p1z and p2z are already regulated
2226 const DataT pHalfPhiSave = regulatePhi(0.5 * (p1phi + p2phi), side); // needs to be regulated since p2phi is not regulated
2227 const DataT pHalfR = 0.5 * (p1r + p2r);
2228
2229 const DataT ezField2 = formulaStruct.evalFieldZ(pHalfZ, pHalfR, pHalfPhiSave);
2230 const DataT eZHalfInv = (isCloseToZero(ezField, ezField2) | isCloseToZero(ezField, fieldez0) | isCloseToZero(ezField, fieldez1)) ? 0 : 1. / (ezField + ezField2);
2231 const DataT fieldSum2ErOverEz = formulaStruct.evalFieldR(pHalfZ, pHalfR, pHalfPhiSave);
2232 const DataT fieldSum2EphiOverEz = formulaStruct.evalFieldPhi(pHalfZ, pHalfR, pHalfPhiSave);
2233
2234 const DataT deltaXSimpsonSixth = (p2z - p1z) / 6;
2235 localIntErOverEz = deltaXSimpsonSixth * (4 * fieldSum2ErOverEz * eZHalfInv + fielder0 * eZ0Inv + fielder1 * eZ1Inv);
2236 localIntEPhiOverEz = deltaXSimpsonSixth * (4 * fieldSum2EphiOverEz * eZHalfInv + fieldephi0 * eZ0Inv + fieldephi1 * eZ1Inv);
2237 localIntDeltaEz = getSign(side) * deltaXSimpsonSixth * (4 * ezField2 + fieldez0 + fieldez1);
2238}
2239
2240template <typename DataT>
2241std::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
2242{
2243 const unsigned int nElectrons = elePos.size();
2244 std::vector<std::pair<std::vector<o2::math_utils::Point3D<float>>, std::array<DataT, 3>>> electronTracks(nElectrons);
2245
2246 for (unsigned int i = 0; i < nElectrons; ++i) {
2247 electronTracks[i].first.reserve(nSamplingPoints + 1);
2248 }
2249
2250 for (unsigned int i = 0; i < nElectrons; ++i) {
2251 const DataT z0 = elePos[i].Z();
2252 const DataT r0 = elePos[i].Rho();
2253 const DataT phi0 = elePos[i].Phi();
2254 const Side side = getSide(z0);
2255 if (!mElectricFieldEr[side].getNDataPoints()) {
2256 LOGP(warning, "E-Fields are not set! Calculation of drift path is not possible");
2257 continue;
2258 }
2259 const NumericalFields<DataT> numEFields{getElectricFieldsInterpolator(side)};
2260 const DataT stepSize = getZMax(side) / nSamplingPoints;
2261
2262 DataT drDist = 0.0; // global distortion dR
2263 DataT dPhiDist = 0.0; // global distortion dPhi (multiplication with R has to be done at the end)
2264 DataT dzDist = 0.0; // global distortion dZ
2265 int iter = 0;
2266 for (;;) {
2267 const DataT z0Tmp = z0 + dzDist + iter * stepSize; // starting z position
2268 const DataT z1Tmp = regulateZ(z0Tmp + stepSize, side); // electron drifts from z0Tmp to z1Tmp
2269 const DataT radius = r0 + drDist; // current radial position of the electron
2270
2271 // abort calculation of drift path if electron reached inner/outer field cage or central electrode
2272 if (radius <= getRMin(side) || radius >= getRMax(side) || getSide(z0Tmp) != side) {
2273 break;
2274 }
2275
2276 const DataT phi = regulatePhi(phi0 + dPhiDist, side); // current phi position of the electron
2277 electronTracks[i].first.emplace_back(GlobalPosition3D(radius * std::cos(phi), radius * std::sin(phi), z0Tmp));
2278
2279 DataT ddR = 0; // distortion dR for drift from z0Tmp to z1Tmp
2280 DataT ddPhi = 0; // distortion dPhi for drift from z0Tmp to z1Tmp
2281 DataT ddZ = 0; // distortion dZ for drift from z0Tmp to z1Tmp
2282
2283 // get the distortion from interpolation of local distortions or calculate distortions with the electric field
2284 processGlobalDistCorr(radius, phi, z0Tmp, z1Tmp, ddR, ddPhi, ddZ, numEFields);
2285
2286 // add local distortions to global distortions
2287 drDist += ddR;
2288 dPhiDist += ddPhi;
2289 dzDist += ddZ;
2290
2291 // if one uses local distortions the interpolated value for the last bin has to be scaled.
2292 // 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.
2293 const bool checkReached = side == Side::A ? z1Tmp >= getZMax(side) : z1Tmp <= getZMax(side);
2294
2295 // 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.
2296 // approximation is done by the current calculated values of the distortions and scaled linear to the 'missing' distance.
2297 if (checkReached) {
2298 const DataT endPoint = z1Tmp + ddZ;
2299 const DataT deltaZ = getZMax(side) - endPoint; // distance from last point to read out
2300 const DataT diff = endPoint - z0Tmp;
2301 const DataT fac = diff != 0 ? std::abs(deltaZ / diff) : 0; // approximate the distortions for the 'missing' distance deltaZ
2302 drDist += ddR * fac;
2303 dPhiDist += ddPhi * fac;
2304 dzDist += ddZ * fac;
2305 const DataT z1TmpEnd = regulateZ(z0Tmp + stepSize, side); // electron drifts from z0Tmp to z1Tmp
2306 const DataT radiusEnd = regulateR(r0 + drDist, side); // current radial position of the electron
2307 const DataT phiEnd = regulatePhi(phi0 + dPhiDist, side); // current phi position of the electron
2308 electronTracks[i].first.emplace_back(GlobalPosition3D(radiusEnd * std::cos(phiEnd), radiusEnd * std::sin(phiEnd), z1TmpEnd));
2309 break;
2310 }
2311 ++iter;
2312 }
2313 electronTracks[i].second = std::array<DataT, 3>{drDist, dPhiDist * r0, dzDist};
2314 }
2315 if (!outFile.empty()) {
2316 dumpElectronTracksToTree(electronTracks, nSamplingPoints, outFile.data());
2317 }
2318 return electronTracks;
2319}
2320
2321template <typename DataT>
2322void 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
2323{
2324 o2::utils::TreeStreamRedirector pcstream(outFile, "RECREATE");
2325 pcstream.GetFile()->cd();
2326
2327 auto& gasParam = ParameterGas::Instance();
2328 auto& eleParam = ParameterElectronics::Instance();
2329
2330 for (int i = 0; i < electronTracks.size(); ++i) {
2331 auto electronPath = electronTracks[i].first;
2332 const int nPoints = electronPath.size();
2333 if (electronPath.empty()) {
2334 LOGP(warning, "Track is empty. Continue to next track.");
2335 continue;
2336 }
2337 std::vector<float> relDriftVel;
2338 relDriftVel.reserve(nPoints);
2339
2340 for (int iPoint = 0; iPoint < (nPoints - 2); ++iPoint) {
2341 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)
2342 relDriftVel.emplace_back(std::abs(relDriftVelTmp));
2343 }
2344
2345 // just copy the last value to avoid wrong values
2346 relDriftVel.emplace_back(relDriftVel.back());
2347 relDriftVel.emplace_back(relDriftVel.back());
2348
2349 DataT distR = electronTracks[i].second[0];
2350 DataT distRPhi = electronTracks[i].second[1];
2351 DataT distZ = electronTracks[i].second[2];
2352
2353 DataT driftTime = std::abs(getZMax(getSide(electronPath.front().Z())) - (distZ + electronPath.front().Z())) / gasParam.DriftV;
2354 DataT timeBin = driftTime / eleParam.ZbinWidth;
2355
2356 pcstream << "drift"
2357 << "electronPath=" << electronPath
2358 << "relDriftVel.=" << relDriftVel // relative drift velocity in z direction
2359 << "distR=" << distR
2360 << "distRPhi=" << distRPhi
2361 << "distZ=" << distZ
2362 << "driftTime=" << driftTime
2363 << "timeBin=" << timeBin
2364 << "\n";
2365 }
2366 pcstream.Close();
2367}
2368
2369template <typename DataT>
2370void SpaceCharge<DataT>::makeElectronDriftPathGif(const char* inpFile, TH2F& hDummy, const int type, const int gifSpeed, const int maxsamplingpoints, const char* outName)
2371{
2372 // read in the tree and convert to vector of std::vector<o2::tpc::GlobalPosition3D>
2373 TFile fInp(inpFile, "READ");
2374 TTree* tree = (TTree*)fInp.Get("drift");
2375 std::vector<o2::tpc::GlobalPosition3D>* electronPathTree = new std::vector<o2::tpc::GlobalPosition3D>;
2376 tree->SetBranchAddress("electronPath", &electronPathTree);
2377
2378 std::vector<std::vector<o2::tpc::GlobalPosition3D>> electronPaths;
2379 std::vector<o2::tpc::GlobalPosition3D> elePosTmp;
2380 const int entries = tree->GetEntriesFast();
2381 for (int i = 0; i < entries; ++i) {
2382 tree->GetEntry(i);
2383 electronPaths.emplace_back(*electronPathTree);
2384 }
2385 delete electronPathTree;
2386 fInp.Close();
2387
2388 TCanvas can("canvas", "canvas", 1000, 600);
2389 can.SetTopMargin(0.04f);
2390 can.SetRightMargin(0.04f);
2391 can.SetBottomMargin(0.12f);
2392 can.SetLeftMargin(0.11f);
2393
2394 const int nElectrons = electronPaths.size();
2395 std::vector<int> indexStartEle(nElectrons);
2396 std::vector<int> countReadoutReached(nElectrons);
2397
2398 // define colors of electrons
2399 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};
2400
2401 // create for each electron an individual graph
2402 unsigned int maxPoints = 0;
2403 std::vector<TGraph> gr(nElectrons);
2404 for (int i = 0; i < nElectrons; ++i) {
2405 gr[i].SetMarkerColor(colorsPalette[i % colorsPalette.size()]);
2406
2407 if (electronPaths[i].size() > maxPoints) {
2408 maxPoints = electronPaths[i].size();
2409 }
2410 }
2411
2412 const DataT pointsPerIteration = maxPoints / static_cast<DataT>(maxsamplingpoints);
2413 std::vector<DataT> zRemainder(nElectrons);
2414
2415 for (;;) {
2416 for (auto& graph : gr) {
2417 graph.Set(0);
2418 }
2419
2420 for (int iEle = 0; iEle < nElectrons; ++iEle) {
2421 const int nSamplingPoints = electronPaths[iEle].size();
2422 const int nPoints = std::round(pointsPerIteration + zRemainder[iEle]);
2423 zRemainder[iEle] = pointsPerIteration - nPoints;
2424 const auto& electronPath = electronPaths[iEle];
2425
2426 if (nPoints == 0 && countReadoutReached[iEle] == 0) {
2427 const int indexPoint = indexStartEle[iEle];
2428 const DataT radius = electronPath[indexPoint].Rho();
2429 const DataT z = electronPath[indexPoint].Z();
2430 const DataT phi = electronPath[indexPoint].Phi();
2431 type == 0 ? gr[iEle].AddPoint(z, radius) : gr[iEle].AddPoint(phi, radius);
2432 }
2433
2434 for (int iPoint = 0; iPoint < nPoints; ++iPoint) {
2435 const int indexPoint = indexStartEle[iEle];
2436 if (indexPoint >= nSamplingPoints) {
2437 countReadoutReached[iEle] = 1;
2438 break;
2439 }
2440
2441 const DataT radius = electronPath[indexPoint].Rho();
2442 const DataT z = electronPath[indexPoint].Z();
2443 const DataT phi = electronPath[indexPoint].Phi();
2444 if (iPoint == nPoints / 2) {
2445 type == 0 ? gr[iEle].AddPoint(z, radius) : gr[iEle].AddPoint(phi, radius);
2446 }
2447 ++indexStartEle[iEle];
2448 }
2449 }
2450 hDummy.Draw();
2451 for (auto& graph : gr) {
2452 if (graph.GetN() > 0) {
2453 graph.Draw("P SAME");
2454 }
2455 }
2456 can.Print(Form("%s.gif+%i", outName, gifSpeed));
2457
2458 const int sumReadoutReached = std::accumulate(countReadoutReached.begin(), countReadoutReached.end(), 0);
2459 if (sumReadoutReached == nElectrons) {
2460 break;
2461 }
2462 }
2463
2464 can.Print(Form("%s.gif++", outName));
2465}
2466
2467
2468
2469template <typename DataT>
2471{
2472 const auto deltaPhi = histoIonsPhiRZ.GetXaxis()->GetBinWidth(1);
2473 const auto deltaZ = histoIonsPhiRZ.GetZaxis()->GetBinWidth(1);
2474 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)]
2475 for (int ir = 1; ir <= histoIonsPhiRZ.GetNbinsY(); ++ir) {
2476 const auto r0 = histoIonsPhiRZ.GetYaxis()->GetBinLowEdge(ir);
2477 const auto r1 = histoIonsPhiRZ.GetYaxis()->GetBinUpEdge(ir);
2478 const auto norm = fac * (r1 * r1 - r0 * r0);
2479 for (int iphi = 1; iphi <= histoIonsPhiRZ.GetNbinsX(); ++iphi) {
2480 for (int iz = 1; iz <= histoIonsPhiRZ.GetNbinsZ(); ++iz) {
2481 const auto charge = histoIonsPhiRZ.GetBinContent(iphi, ir, iz);
2482 histoIonsPhiRZ.SetBinContent(iphi, ir, iz, charge / norm);
2483 }
2484 }
2485 }
2486}
2487
2488template <typename DataT>
2490{
2491 if (!mAnaDistCorr.isValid()) {
2492 LOGP(info, "============== analytical functions are not set! returning ==============");
2493 return 0;
2494 }
2495 bool isOK = outf.WriteObject(&mAnaDistCorr, "analyticalDistCorr");
2496 return isOK;
2497}
2498
2499template <typename DataT>
2501{
2502 TFile fIn(inpf.data(), "READ");
2503 const bool containsFormulas = fIn.GetListOfKeys()->Contains("analyticalDistCorr");
2504 if (!containsFormulas) {
2505 LOGP(info, "============== analytical functions are not stored! returning ==============");
2506 return;
2507 }
2508 LOGP(info, "Using analytical corrections and distortions");
2509 setUseAnalyticalDistCorr(true);
2510 AnalyticalDistCorr<DataT>* form = (AnalyticalDistCorr<DataT>*)fIn.Get("analyticalDistCorr");
2511 mAnaDistCorr = *form;
2512 delete form;
2513}
2514
2515template <typename DataT>
2516void SpaceCharge<DataT>::langevinCylindricalE(DataT& ddR, DataT& ddPhi, DataT& ddZ, const DataT radius, const DataT localIntErOverEz, const DataT localIntEPhiOverEz, const DataT localIntDeltaEz) const
2517{
2518 // calculated distortions/correction with the formula described in https://edms.cern.ch/ui/file/1108138/1/ALICE-INT-2010-016.pdf page 7.
2519 ddR = mC0 * localIntErOverEz + mC1 * localIntEPhiOverEz;
2520 ddPhi = (mC0 * localIntEPhiOverEz - mC1 * localIntErOverEz) / radius;
2521 ddZ = -localIntDeltaEz * TPCParameters<DataT>::DVDE;
2522}
2523
2524template <typename DataT>
2525void SpaceCharge<DataT>::langevinCylindricalB(DataT& ddR, DataT& ddPhi, const DataT radius, const DataT localIntBrOverBz, const DataT localIntBPhiOverBz) const
2526{
2527 // calculated distortions/correction with the formula described in https://edms.cern.ch/ui/file/1108138/1/ALICE-INT-2010-016.pdf page 7.
2528 ddR = mC2 * localIntBrOverBz - mC1 * localIntBPhiOverBz;
2529 ddPhi = (mC2 * localIntBPhiOverBz + mC1 * localIntBrOverBz) / radius;
2530}
2531
2532template <typename DataT>
2534{
2535 const std::unordered_map<int, std::pair<int, int>> field_to_current = {{2, {12000, 6000}},
2536 {5, {30000, 6000}},
2537 {-2, {-12000, -6000}},
2538 {-5, {-30000, -6000}},
2539 {0, {0, 0}}};
2540 auto currents_iter = field_to_current.find(field);
2541 if (currents_iter == field_to_current.end()) {
2542 LOG(error) << " Could not lookup currents for fieldvalue " << field;
2543 return;
2544 }
2545 mBField.setBField(field);
2547 magField.setL3Current((*currents_iter).second.first);
2548 magField.setDipoleCurrent((*currents_iter).second.second);
2549 setBFields(magField);
2550}
2551
2552template <typename DataT>
2554{
2555 const float bzField = int(magField.getNominalL3Field());
2556 auto& gasParam = ParameterGas::Instance();
2557 float vDrift = gasParam.DriftV; // drift velocity in cm/us
2558 const float omegaTau = -10. * bzField * vDrift / std::abs(getEzField(Side::A));
2559 LOGP(detail, "Setting omegaTau to {} for {}kG", omegaTau, bzField);
2560 const float t1 = 1.;
2561 const float t2 = 1.;
2562 setOmegaTauT1T2(omegaTau, t1, t2);
2563}
2564
2565template <typename DataT>
2566template <typename Fields>
2567void 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
2568{
2569 // see: https://edms.cern.ch/ui/file/1108138/1/ALICE-INT-2010-016.pdf
2570 // needed for calculation of distortions/corrections
2571 DataT localIntErOverEz = 0; // integral_p1z^p2z Er/Ez dz
2572 DataT localIntEPhiOverEz = 0; // integral_p1z^p2z Ephi/Ez dz
2573 DataT localIntDeltaEz = 0; // integral_p1z^p2z Ez dz
2574
2575 DataT localIntBrOverBz = 0; // integral_p1z^p2z Br/Bz dz
2576 DataT localIntBPhiOverBz = 0; // integral_p1z^p2z Bphi/Bz dz
2577 DataT localIntDeltaBz = 0; // integral_p1z^p2z Bz dz
2578 DataT ddRExB = 0;
2579 DataT ddPhiExB = 0;
2580
2581 // there are differentnumerical integration strategys implements. for details see each function.
2582 switch (sNumericalIntegrationStrategy) {
2583 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)
2584 for (int i = 0; i < sSimpsonNIteratives; ++i) { // TODO define a convergence criterion to abort the algorithm earlier for speed up.
2585 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)
2586 if (mSimEDistortions) {
2587 integrateEFieldsSimpsonIterative(p1r, p1r + ddR + ddRExB, p1phi, p1phi + ddPhi + ddPhiExB, p1z, tmpZ, localIntErOverEz, localIntEPhiOverEz, localIntDeltaEz, formulaStruct, getEzField(side), side);
2588 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
2589 }
2590 if (mSimExBMisalignment) {
2591 integrateEFieldsSimpsonIterative(p1r, p1r + ddR + ddRExB, p1phi, p1phi + ddPhi + ddPhiExB, p1z, tmpZ, localIntBrOverBz, localIntBPhiOverBz, localIntDeltaBz, mBField, 0, side);
2592 langevinCylindricalB(ddRExB, ddPhiExB, (p1r + 0.5 * (ddR + ddRExB)), localIntBrOverBz, localIntBPhiOverBz);
2593 }
2594 }
2595 break;
2596 case IntegrationStrategy::Simpson: // simpson integration
2597 if (mSimEDistortions) {
2598 integrateEFieldsSimpson(p1r, p1phi, p1z, p2z, localIntErOverEz, localIntEPhiOverEz, localIntDeltaEz, formulaStruct, getEzField(side), side);
2599 langevinCylindricalE(ddR, ddPhi, ddZ, p1r, localIntErOverEz, localIntEPhiOverEz, localIntDeltaEz);
2600 }
2601 if (mSimExBMisalignment) {
2602 integrateEFieldsSimpson(p1r, p1phi, p1z, p2z, localIntBrOverBz, localIntBPhiOverBz, localIntDeltaBz, mBField, 0, side);
2603 langevinCylindricalB(ddRExB, ddPhiExB, p1r, localIntBrOverBz, localIntBPhiOverBz);
2604 }
2605 break;
2606 case IntegrationStrategy::Trapezoidal: // trapezoidal integration (fastest)
2607 if (mSimEDistortions) {
2608 integrateEFieldsTrapezoidal(p1r, p1phi, p1z, p2z, localIntErOverEz, localIntEPhiOverEz, localIntDeltaEz, formulaStruct, getEzField(side), side);
2609 langevinCylindricalE(ddR, ddPhi, ddZ, p1r, localIntErOverEz, localIntEPhiOverEz, localIntDeltaEz);
2610 }
2611 if (mSimExBMisalignment) {
2612 integrateEFieldsTrapezoidal(p1r, p1phi, p1z, p2z, localIntBrOverBz, localIntBPhiOverBz, localIntDeltaBz, mBField, 0, side);
2613 langevinCylindricalB(ddRExB, ddPhiExB, p1r, localIntBrOverBz, localIntBPhiOverBz);
2614 }
2615 break;
2616 case IntegrationStrategy::Root: // using integration implemented in ROOT (slow)
2617 if (mSimEDistortions) {
2618 integrateEFieldsRoot(p1r, p1phi, p1z, p2z, localIntErOverEz, localIntEPhiOverEz, localIntDeltaEz, formulaStruct, getEzField(side), side);
2619 langevinCylindricalE(ddR, ddPhi, ddZ, p1r, localIntErOverEz, localIntEPhiOverEz, localIntDeltaEz);
2620 }
2621 if (mSimExBMisalignment) {
2622 integrateEFieldsRoot(p1r, p1phi, p1z, p2z, localIntBrOverBz, localIntBPhiOverBz, localIntDeltaBz, mBField, 0, side);
2623 langevinCylindricalB(ddRExB, ddPhiExB, p1r, localIntBrOverBz, localIntBPhiOverBz);
2624 }
2625 break;
2626 default:
2627 if (mSimEDistortions) {
2628 integrateEFieldsSimpson(p1r, p1phi, p1z, p2z, localIntErOverEz, localIntEPhiOverEz, localIntDeltaEz, formulaStruct, getEzField(side), side);
2629 langevinCylindricalE(ddR, ddPhi, ddZ, p1r, localIntErOverEz, localIntEPhiOverEz, localIntDeltaEz);
2630 }
2631 if (mSimExBMisalignment) {
2632 integrateEFieldsSimpson(p1r, p1phi, p1z, p2z, localIntBrOverBz, localIntBPhiOverBz, localIntDeltaBz, mBField, 0, side);
2633 langevinCylindricalB(ddRExB, ddPhiExB, p1r, localIntBrOverBz, localIntBPhiOverBz);
2634 }
2635 }
2636
2637 GPUCA_DEBUG_STREAMER_CHECK(if (o2::utils::DebugStreamer::checkStream(o2::utils::StreamFlags::streamDistortionsSC)) {
2638 o2::utils::DebugStreamer::instance()->getStreamer("debug_calcDistCorr", "UPDATE") << o2::utils::DebugStreamer::instance()->getUniqueTreeName("debug_calcDistCorr").data()
2639 << "p1r=" << (*const_cast<DataT*>(&p1r))
2640 << "p1phi=" << (*const_cast<DataT*>(&p1phi))
2641 << "p1z=" << (*const_cast<DataT*>(&p1z))
2642 << "p2z=" << (*const_cast<DataT*>(&p2z))
2643 << "localIntErOverEz=" << localIntErOverEz
2644 << "localIntEPhiOverEz=" << localIntEPhiOverEz
2645 << "localIntDeltaEz=" << localIntDeltaEz
2646 << "ddR=" << ddR
2647 << "ddPhi=" << ddPhi
2648 << "ddZ=" << ddZ
2649 << "localIntBrOverBz=" << localIntBrOverBz
2650 << "localIntBPhiOverBz=" << localIntBPhiOverBz
2651 << "localIntDeltaBz=" << localIntDeltaBz
2652 << "ddRExB=" << ddRExB
2653 << "ddPhiExB=" << ddPhiExB
2654 << "\n";
2655 })
2656
2657 ddR += ddRExB;
2658 ddPhi += ddPhiExB;
2659}
2660
2661
2662
2663
2664
2665template <typename DataT>
2666template <typename DataTIn>
2668{
2669 initContainer(mGlobalDistdR[side], false);
2670 initContainer(mGlobalDistdZ[side], false);
2671 initContainer(mGlobalDistdRPhi[side], false);
2672 const std::string sideName = getSideName(side);
2673 mGlobalDistdR[side].template initFromFile<DataTIn>(inpf, fmt::format("distR_side{}", sideName).data());
2674 mGlobalDistdZ[side].template initFromFile<DataTIn>(inpf, fmt::format("distZ_side{}", sideName).data());
2675 mGlobalDistdRPhi[side].template initFromFile<DataTIn>(inpf, fmt::format("distRphi_side{}", sideName).data());
2676}
2677
2678
2679
2680template <typename DataT>
2681template <typename DataTIn>
2683{
2684 initContainer(mGlobalCorrdR[side], false);
2685 initContainer(mGlobalCorrdZ[side], false);
2686 initContainer(mGlobalCorrdRPhi[side], false);
2687 const std::string sideName = getSideName(side);
2688 mGlobalCorrdR[side].template initFromFile<DataTIn>(inpf, fmt::format("corrR_side{}", sideName).data());
2689 mGlobalCorrdZ[side].template initFromFile<DataTIn>(inpf, fmt::format("corrZ_side{}", sideName).data());
2690 mGlobalCorrdRPhi[side].template initFromFile<DataTIn>(inpf, fmt::format("corrRPhi_side{}", sideName).data());
2691}
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702template <typename DataT>
2703bool SpaceCharge<DataT>::checkGridFromFile(std::string_view file, std::string_view tree)
2704{
2705 unsigned short nr, nz, nphi;
2706 if (!DataContainer::getVertices(tree, file, nr, nz, nphi)) {
2707 return false;
2708 }
2709
2710 // check if stored grid definition and current grid definition is the same
2711 if ((mParamGrid.NRVertices != nr) || (mParamGrid.NZVertices != nz) || (mParamGrid.NPhiVertices != nphi)) {
2712 LOGP(info, "Different number of vertices found in input file. Initializing new space charge object with nR {} nZ {} nPhi {} vertices", nr, nz, nphi);
2713 SpaceCharge<DataT> scTmp(mBField.getBField(), nz, nr, nphi, false);
2714 scTmp.mC0 = mC0;
2715 scTmp.mC1 = mC1;
2716 scTmp.mC2 = mC2;
2717 *this = std::move(scTmp);
2718 }
2719 return true;
2720}
2721
2722
2723template <typename DataT>
2725{
2726 if (!mGlobalCorrdR[side].getNDataPoints()) {
2727 LOGP(info, "============== global corrections are not set! returning ==============");
2728 return 0;
2729 }
2730 const std::string sideName = getSideName(side);
2731 const int er = mGlobalCorrdR[side].template writeToFile<float>(outf, fmt::format("corrR_side{}", sideName).data());
2732 const int ez = mGlobalCorrdZ[side].template writeToFile<float>(outf, fmt::format("corrZ_side{}", sideName).data());
2733 const int ephi = mGlobalCorrdRPhi[side].template writeToFile<float>(outf, fmt::format("corrRPhi_side{}", sideName).data());
2734 return er + ez + ephi;
2735}
2736
2737
2738
2739
2740
2741template <typename DataT>
2743{
2744 LOGP(warning, "Use this feature only if you know what you are doing!");
2746 RegularGrid gridTmp[FNSIDES]{{GridProp::ZMIN, GridProp::RMIN, GridProp::PHIMIN, getSign(Side::A) * GridProp::getGridSpacingZ(mParamGrid.NZVertices), GridProp::getGridSpacingR(mParamGrid.NRVertices), GridProp::getGridSpacingPhi(mParamGrid.NPhiVertices) / SECTORSPERSIDE * nSectors, mParamGrid},
2747 {GridProp::ZMIN, GridProp::RMIN, GridProp::PHIMIN, getSign(Side::C) * GridProp::getGridSpacingZ(mParamGrid.NZVertices), GridProp::getGridSpacingR(mParamGrid.NRVertices), GridProp::getGridSpacingPhi(mParamGrid.NPhiVertices) / SECTORSPERSIDE * nSectors, mParamGrid}};
2748 mGrid3D[0] = gridTmp[0];
2749 mGrid3D[1] = gridTmp[1];
2750}
2751
2752template <typename DataT>
2757
2758
2759
2760template <typename DataT>
2761void SpaceCharge<DataT>::initContainer(DataContainer& data, const bool initMem)
2762{
2763 if (!data.getNDataPoints()) {
2764 data.setGrid(getNZVertices(), getNRVertices(), getNPhiVertices(), initMem);
2765 }
2766}
2767
2768template <typename DataT>
2769void SpaceCharge<DataT>::setOmegaTauT1T2(const DataT omegaTau, const DataT t1, const DataT t2)
2770{
2771 const DataT wt0 = t2 * omegaTau;
2772 const DataT wt02 = wt0 * wt0;
2773 mC0 = 1 / (1 + wt02);
2774 const DataT wt1 = t1 * omegaTau;
2775 mC1 = wt1 / (1 + wt1 * wt1);
2776 mC2 = wt02 / (1 + wt02);
2777}
2778
2779template <typename DataT>
2781{
2782 const bool sameGrid = (getNPhiVertices() == otherSC.getNPhiVertices()) && (getNRVertices() == otherSC.getNRVertices()) && (getNZVertices() == otherSC.getNZVertices());
2783 if (!sameGrid) {
2784 LOGP(warning, "Space charge objects have different grid definition");
2785 return;
2786 }
2787
2788 mDensity[Side::A] += otherSC.mDensity[Side::A];
2789 mDensity[Side::C] += otherSC.mDensity[Side::C];
2790}
2791
2792template <typename DataT>
2794{
2795 const bool sameGrid = (getNPhiVertices() == otherSC.getNPhiVertices()) && (getNRVertices() == otherSC.getNRVertices()) && (getNZVertices() == otherSC.getNZVertices());
2796 if (!sameGrid) {
2797 LOGP(warning, "Space charge objects have different grid definition");
2798 return;
2799 }
2800
2801 mGlobalCorrdR[side] += otherSC.mGlobalCorrdR[side];
2802 mGlobalCorrdZ[side] += otherSC.mGlobalCorrdZ[side];
2803 mGlobalCorrdRPhi[side] += otherSC.mGlobalCorrdRPhi[side];
2804}
2805
2806template <typename DataT>
2807void SpaceCharge<DataT>::fillChargeDensityFromHisto(const char* file, const char* nameA, const char* nameC)
2808{
2809 TFile fInp(file, "READ");
2810 TH3F* hSCA = (TH3F*)fInp.Get(nameA);
2811 TH3F* hSCC = (TH3F*)fInp.Get(nameC);
2812 if (!hSCA) {
2813 LOGP(error, "Histogram {} not found", nameA);
2814 }
2815 if (!hSCC) {
2816 LOGP(error, "Histogram {} not found", nameC);
2817 }
2818 fillChargeDensityFromHisto(*hSCA, *hSCC);
2819}
2820
2821template <typename DataT>
2822void SpaceCharge<DataT>::fillChargeDensityFromHisto(const TH3& hisSCDensity3D_A, const TH3& hisSCDensity3D_C)
2823{
2824 const int nPhiBinsTmp = hisSCDensity3D_A.GetXaxis()->GetNbins();
2825 const int nRBinsTmp = hisSCDensity3D_A.GetYaxis()->GetNbins();
2826 const int nZBins = hisSCDensity3D_A.GetZaxis()->GetNbins();
2827 const auto phiLow = hisSCDensity3D_A.GetXaxis()->GetBinLowEdge(1);
2828 const auto phiUp = hisSCDensity3D_A.GetXaxis()->GetBinUpEdge(nPhiBinsTmp);
2829 const auto rLow = hisSCDensity3D_A.GetYaxis()->GetBinLowEdge(1);
2830 const auto rUp = hisSCDensity3D_A.GetYaxis()->GetBinUpEdge(nRBinsTmp);
2831 const auto zUp = hisSCDensity3D_A.GetZaxis()->GetBinUpEdge(nZBins);
2832
2833 TH3F hisSCMerged("hisMerged", "hisMerged", nPhiBinsTmp, phiLow, phiUp, nRBinsTmp, rLow, rUp, 2 * nZBins, -zUp, zUp);
2834
2835 for (int iside = 0; iside < FNSIDES; ++iside) {
2836 const auto& hSC = (iside == 0) ? hisSCDensity3D_A : hisSCDensity3D_C;
2837#pragma omp parallel for num_threads(sNThreads)
2838 for (int iz = 1; iz <= nZBins; ++iz) {
2839 const int izTmp = (iside == 0) ? (nZBins + iz) : iz;
2840 for (int ir = 1; ir <= nRBinsTmp; ++ir) {
2841 for (int iphi = 1; iphi <= nPhiBinsTmp; ++iphi) {
2842 hisSCMerged.SetBinContent(iphi, ir, izTmp, hSC.GetBinContent(iphi, ir, iz));
2843 }
2844 }
2845 }
2846 }
2847 fillChargeDensityFromHisto(hisSCMerged);
2848}
2849
2850template <typename DataT>
2851void SpaceCharge<DataT>::convertIDCsToCharge(std::vector<CalDet<float>>& idcZero, const CalDet<float>& mapIBF, const float ionDriftTimeMS, const bool normToPadArea)
2852{
2853 // 1. integration time per IDC interval in ms
2854 const int nOrbits = 12;
2855 const float idcIntegrationTimeMS = (nOrbits * o2::constants::lhc::LHCOrbitMUS) / 1e3;
2856
2857 // number of time stamps for each integration interval (5346) (see: IDCSim.h getNTimeStampsPerIntegrationInterval())
2858 const unsigned int nTimeStampsPerIDCInterval{(o2::constants::lhc::LHCMaxBunches * nOrbits) / o2::tpc::constants::LHCBCPERTIMEBIN};
2859
2860 const int nIDCSlices = idcZero.size();
2861 // IDCs are normalized for each interval to 5346 time bins
2862 // IDC0 = <IDC> per ms = 5346 / ~1ms
2863 const float idcsPerMS = nTimeStampsPerIDCInterval / idcIntegrationTimeMS;
2864
2865 // length of one z slice on ms
2866 const float lengthZSliceMS = ionDriftTimeMS / nIDCSlices;
2867
2868 // IDCs for one z slice
2869 const float scaleToIonDrift = lengthZSliceMS * idcsPerMS;
2870
2871 // get conversion factor from ADC to electrons (see: SAMPAProcessing::getADCvalue())
2872 const static ParameterElectronics& parameterElectronics = ParameterElectronics::Instance();
2873 const float conversionADCToEle = parameterElectronics.ElectronCharge * 1.e15 * parameterElectronics.ChipGain * parameterElectronics.ADCsaturation / parameterElectronics.ADCdynamicRange;
2874
2875 const float conversionFactor = scaleToIonDrift / conversionADCToEle;
2876 LOGP(info, "Converting IDCs to space-charge density with conversion factor of {}", conversionFactor);
2877
2878 for (auto& calIDC : idcZero) {
2879 if (normToPadArea) {
2880 for (unsigned int sector = 0; sector < Mapper::NSECTORS; ++sector) {
2881 for (unsigned int region = 0; region < Mapper::NREGIONS; ++region) {
2882 for (int lrow = 0; lrow < Mapper::ROWSPERREGION[region]; ++lrow) {
2883 for (unsigned int pad = 0; pad < Mapper::PADSPERROW[region][lrow]; ++pad) {
2884 const int globalPad = Mapper::getGlobalPadNumber(lrow, pad, region);
2885 float idcTmp = calIDC.getValue(sector, globalPad);
2886 calIDC.setValue(sector, globalPad, conversionFactor * idcTmp / Mapper::INVPADAREA[region]);
2887 }
2888 }
2889 }
2890 }
2891 } else {
2892 calIDC *= conversionFactor;
2893 }
2894 // take IBF into account
2895 calIDC *= mapIBF;
2896 calIDC *= 0.01f; // ibf values are in %
2897 }
2898}
2899
2900template <typename DataT>
2901void SpaceCharge<DataT>::fillChargeFromIDCs(std::vector<CalDet<float>>& idcZero, const CalDet<float>& mapIBF, const float ionDriftTimeMS, const bool normToPadArea)
2902{
2903 convertIDCsToCharge(idcZero, mapIBF, ionDriftTimeMS, normToPadArea);
2904 fillChargeFromCalDet(idcZero);
2905}
2906
2907template <typename DataT>
2908void SpaceCharge<DataT>::initRodAlignmentVoltages(const MisalignmentType misalignmentType, const FCType fcType, const int sector, const Side side, const float deltaPot)
2909{
2910 // see also original implementation in AliTPCFCVoltError3D::InitFCVoltError3D (https://github.com/alisw/AliRoot/blob/master/TPC/TPCbase/AliTPCFCVoltError3D.h)
2911
2912 const int iside = static_cast<int>(side);
2913 initContainer(mPotential[iside], true);
2914 const int phiVerticesPerSector = mParamGrid.NPhiVertices / SECTORSPERSIDE;
2915 int phiVerticesEnd = phiVerticesPerSector;
2916
2917 if (misalignmentType == MisalignmentType::RodShift) {
2918 const float rodDiameter = 1; // 1 cm SET SOMEWEHERE ELSE
2919 const float rodRadius = rodDiameter / 2;
2920 const float radiusTmp = (fcType == FCType::IFC) ? TPCParameters<DataT>::IFCRADIUS : TPCParameters<DataT>::OFCRADIUS;
2921 int nPhiVerticesPerRod = static_cast<int>(rodRadius * mParamGrid.NPhiVertices / (TWOPI * radiusTmp) + 0.5);
2922 if (nPhiVerticesPerRod == 0) {
2923 nPhiVerticesPerRod = 1;
2924 }
2925 phiVerticesEnd = nPhiVerticesPerRod;
2926 }
2927
2928 const int phiStart = sector * phiVerticesPerSector;
2929 const int phiEnd = phiStart + phiVerticesEnd;
2930 const int nRVertex = (fcType == FCType::IFC) ? 0 : (mParamGrid.NRVertices - 1);
2931 for (size_t iPhi = phiStart; iPhi < phiEnd; ++iPhi) {
2932 const int iPhiSector = iPhi % phiVerticesPerSector;
2933
2934 float potentialSector = 0;
2935 float potentialLastSector = 0;
2936 if ((misalignmentType == MisalignmentType::ShiftedClip) || (misalignmentType == MisalignmentType::RodShift)) {
2937 const float potentialShiftedClips = deltaPot - iPhiSector * deltaPot / phiVerticesEnd;
2938 potentialSector = potentialShiftedClips;
2939 potentialLastSector = potentialShiftedClips;
2940 } else if (misalignmentType == MisalignmentType::RotatedClip) {
2941 // set to zero for first vertex
2942 if (iPhiSector == 0) {
2943 potentialSector = 0;
2944 potentialLastSector = 0;
2945 } else {
2946 const float potentialRotatedClip = -deltaPot + (iPhiSector - 1) * deltaPot / (phiVerticesPerSector - 2);
2947 potentialSector = potentialRotatedClip;
2948 potentialLastSector = -potentialRotatedClip;
2949 }
2950 }
2951
2952 for (size_t iZ = 0; iZ < mParamGrid.NZVertices; ++iZ) {
2953 mPotential[iside](iZ, nRVertex, iPhi) += potentialSector;
2954 if (iPhiSector > 0) {
2955 const int iPhiMirror = ((phiStart - iPhiSector) + mParamGrid.NPhiVertices) % mParamGrid.NPhiVertices;
2956 mPotential[iside](iZ, nRVertex, iPhiMirror) += potentialLastSector;
2957 }
2958 }
2959 }
2960}
2961
2962template <typename DataT>
2964{
2965 if (other.mPotential[side].getData().empty()) {
2966 LOGP(info, "Other space-charge object is empty!");
2967 return;
2968 }
2969
2970 if ((mParamGrid.NRVertices != other.mParamGrid.NRVertices) || (mParamGrid.NZVertices != other.mParamGrid.NZVertices) || (mParamGrid.NPhiVertices != other.mParamGrid.NPhiVertices)) {
2971 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);
2972 SpaceCharge<DataT> scTmp(mBField.getBField(), other.mParamGrid.NZVertices, other.mParamGrid.NRVertices, other.mParamGrid.NPhiVertices, false);
2973 scTmp.mC0 = mC0;
2974 scTmp.mC1 = mC1;
2975 scTmp.mC2 = mC2;
2976 *this = std::move(scTmp);
2977 }
2978
2979 initContainer(mPotential[side], true);
2980
2981 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
2982 for (size_t iZ = 1; iZ < mParamGrid.NZVertices; ++iZ) {
2983 const size_t iRFirst = 0;
2984 mPotential[side](iZ, iRFirst, iPhi) += scaling * other.mPotential[side](iZ, iRFirst, iPhi);
2985
2986 const size_t iRLast = mParamGrid.NRVertices - 1;
2987 mPotential[side](iZ, iRLast, iPhi) += scaling * other.mPotential[side](iZ, iRLast, iPhi);
2988 }
2989 }
2990
2991 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
2992 for (size_t iR = 0; iR < mParamGrid.NRVertices; ++iR) {
2993 const size_t iZFirst = 0;
2994 mPotential[side](iZFirst, iR, iPhi) += scaling * other.mPotential[side](iZFirst, iR, iPhi);
2995
2996 const size_t iZLast = mParamGrid.NZVertices - 1;
2997 mPotential[side](iZLast, iR, iPhi) += scaling * other.mPotential[side](iZLast, iR, iPhi);
2998 }
2999 }
3000}
3001
3002template <typename DataT>
3004{
3005 const float zMaxAbs = std::abs(zMax);
3006 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
3007 for (size_t iZ = 1; iZ < mParamGrid.NZVertices; ++iZ) {
3008 const DataT z = std::abs(getZVertex(iZ, side));
3009 if ((z < zMin) || (z > zMax)) {
3010 const size_t iRFirst = 0;
3011 mPotential[side](iZ, iRFirst, iPhi) = 0;
3012
3013 const size_t iRLast = mParamGrid.NRVertices - 1;
3014 mPotential[side](iZ, iRLast, iPhi) = 0;
3015 }
3016 }
3017 }
3018
3019 for (size_t iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
3020 for (size_t iR = 0; iR < mParamGrid.NRVertices; ++iR) {
3021 const size_t iZFirst = 0;
3022 const float zFirst = std::abs(getZVertex(iZFirst, side));
3023 if ((zFirst < zMin) || (zFirst > zMax)) {
3024 mPotential[side](iZFirst, iR, iPhi) = 0;
3025 }
3026
3027 const size_t iZLast = mParamGrid.NZVertices - 1;
3028 const float zLast = std::abs(getZVertex(iZLast, side));
3029 if ((zLast < zMin) || (zLast > zMax)) {
3030 mPotential[side](iZLast, iR, iPhi) = 0;
3031 }
3032 }
3033 }
3034}
3035
3036template <typename DataT>
3037void SpaceCharge<DataT>::setIFCChargeUpRisingPot(const float deltaPot, const float zMaxDeltaPot, const int type, const float zStart, const float offs, const Side side)
3038{
3039 std::function<DataT(DataT)> chargeUpIFCLinear = [zStart, type, offs, deltaPot, zMaxDeltaPot](const DataT z) {
3040 const float absZ = std::abs(z);
3041 const float absZMaxDeltaPot = std::abs(zMaxDeltaPot);
3042 if ((absZ <= absZMaxDeltaPot) && (absZ >= zStart)) {
3043 // 1/x
3044 if (type == 1) {
3045 const float offsZ = 1;
3046 const float zMaxDeltaPotTmp = zMaxDeltaPot - zStart + offsZ;
3047 const float p1 = deltaPot / (1 / offsZ - 1 / zMaxDeltaPotTmp);
3048 const float p2 = -p1 / zMaxDeltaPotTmp;
3049 const float absZShifted = zMaxDeltaPotTmp - (absZ - zStart);
3050 DataT pot = p2 + p1 / absZShifted;
3051 return pot;
3052 } else if (type == 0 || type == 4) {
3053 // linearly rising potential
3054 return static_cast<DataT>(deltaPot / (absZMaxDeltaPot - zStart) * (absZ - zStart) + offs);
3055 } else if (type == 2) {
3056 // flat
3057 return DataT(deltaPot);
3058 } else if (type == 3) {
3059 // linear falling
3060 return static_cast<DataT>(-deltaPot / (absZMaxDeltaPot - zStart) * (absZ - zStart) + deltaPot);
3061 } else {
3062 return DataT(0);
3063 }
3064 } else if (type == 4) {
3065 // flat no z dependence
3066 return DataT(offs);
3067 } else {
3068 return DataT(0);
3069 }
3070 };
3071 setPotentialBoundaryInnerRadius(chargeUpIFCLinear, side);
3072}
3073
3074template <typename DataT>
3075void SpaceCharge<DataT>::setIFCChargeUpFallingPot(const float deltaPot, const float zMaxDeltaPot, const int type, const float zEnd, const float offs, const Side side)
3076{
3077 std::function<DataT(DataT)> chargeUpIFCLinear = [zEnd, type, offs, zMax = getZMax(Side::A), deltaPot, zMaxDeltaPot](const DataT z) {
3078 const float absZ = std::abs(z);
3079 const float absZMaxDeltaPot = std::abs(zMaxDeltaPot);
3080
3081 bool check = (absZ >= absZMaxDeltaPot);
3082 if (type == 0 || type == 3) {
3083 check = (absZ >= absZMaxDeltaPot);
3084 }
3085
3086 if (check && (absZ <= zEnd)) {
3087 // 1/x dependency
3088 if (type == 1) {
3089 const float p1 = (deltaPot - offs) / (1 / zMaxDeltaPot - 1 / zEnd);
3090 const float p2 = offs - p1 / zEnd;
3091 DataT pot = p2 + p1 / absZ;
3092 return pot;
3093 } else if (type == 2) {
3094 // 1/x dependency steep fall off!
3095 const float offsZ = 1 + offs;
3096 const float zEndTmp = zEnd - zMaxDeltaPot + offsZ;
3097 const float p1 = deltaPot / (1 / offsZ - 1 / zEndTmp);
3098 const float p2 = -p1 / zEndTmp;
3099 const float absZShifted = absZ - zMaxDeltaPot + offsZ;
3100 DataT pot = p2 + p1 / absZShifted;
3101 return pot;
3102 } else if (type == 0 || type == 3) {
3103 // linearly falling potential
3104 const float zPos = absZ - zEnd;
3105 return static_cast<DataT>(deltaPot / (absZMaxDeltaPot - zEnd) * zPos + offs);
3106 } else {
3107 return DataT(0);
3108 }
3109 } else if (type == 3) {
3110 return DataT(offs);
3111 } else {
3112 return DataT(0);
3113 }
3114 };
3115 setPotentialBoundaryInnerRadius(chargeUpIFCLinear, side);
3116}
3117
3118template <typename DataT>
3119void 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)
3120{
3121 setGlobalDistCorr(Type::Corrections, gCorr, side);
3122}
3123
3124template <typename DataT>
3125void SpaceCharge<DataT>::setGlobalDistortions(const std::function<void(int sector, DataT gx, DataT gy, DataT gz, DataT& gCx, DataT& gCy, DataT& gCz)>& gDist, const Side side)
3126{
3127 setGlobalDistCorr(Type::Distortions, gDist, side);
3128}
3129
3130template <typename DataT>
3131void SpaceCharge<DataT>::setGlobalDistCorr(const Type type, const std::function<void(int sector, DataT gx, DataT gy, DataT gz, DataT& gCx, DataT& gCy, DataT& gCz)>& gFunc, const Side side)
3132{
3133 if (type == Type::Distortions) {
3134 initContainer(mGlobalDistdR[side], true);
3135 initContainer(mGlobalDistdZ[side], true);
3136 initContainer(mGlobalDistdRPhi[side], true);
3137 } else {
3138 initContainer(mGlobalCorrdR[side], true);
3139 initContainer(mGlobalCorrdZ[side], true);
3140 initContainer(mGlobalCorrdRPhi[side], true);
3141 }
3142
3143#pragma omp parallel for num_threads(sNThreads)
3144 for (unsigned int iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) {
3145 const int sector = iPhi / (mParamGrid.NPhiVertices / SECTORSPERSIDE) + (side == Side::A ? 0 : SECTORSPERSIDE);
3146 DataT phi = getPhiVertex(iPhi, side);
3147 phi = o2::math_utils::detail::toPMPi(phi);
3148
3149 for (unsigned int iR = 0; iR < mParamGrid.NRVertices; ++iR) {
3150 const DataT radius = getRVertex(iR, side);
3151 const DataT x = getXFromPolar(radius, phi);
3152 const DataT y = getYFromPolar(radius, phi);
3153
3154 for (unsigned int iZ = 0; iZ < mParamGrid.NZVertices; ++iZ) {
3155 const DataT z = getZVertex(iZ, side);
3156
3157 // get corrected points
3158 DataT gCx = 0;
3159 DataT gCy = 0;
3160 DataT gCz = 0;
3161 gFunc(sector, x, y, z, gCx, gCy, gCz);
3162 const DataT gCxCorr = x + gCx;
3163 const DataT gCyCorr = y + gCy;
3164
3165 // get corrections
3166 const DataT corrR = getRadiusFromCartesian(gCxCorr, gCyCorr) - radius;
3167 DataT phiDiff = getPhiFromCartesian(gCxCorr, gCyCorr) - phi;
3168 phiDiff = o2::math_utils::detail::toPMPi(phiDiff);
3169 const DataT corrRPhi = phiDiff * radius;
3170
3171 // store corrections
3172 if (type == Type::Distortions) {
3173 mGlobalDistdR[side](iZ, iR, iPhi) = corrR;
3174 mGlobalDistdZ[side](iZ, iR, iPhi) = gCz;
3175 mGlobalDistdRPhi[side](iZ, iR, iPhi) = corrRPhi;
3176 } else {
3177 mGlobalCorrdR[side](iZ, iR, iPhi) = corrR;
3178 mGlobalCorrdZ[side](iZ, iR, iPhi) = gCz;
3179 mGlobalCorrdRPhi[side](iZ, iR, iPhi) = corrRPhi;
3180 }
3181 }
3182 }
3183 }
3184}
3185
3186template <typename DataT>
3187void SpaceCharge<DataT>::setROCMisalignmentShiftZ(const int sector, const int type, const float potential)
3188{
3189 setROCMisalignment(type, 2, sector, potential, potential);
3190}
3191
3192template <typename DataT>
3193void SpaceCharge<DataT>::setROCMisalignmentRotationAlongX(const int sector, const int type, const float potentialMin, const float potentialMax)
3194{
3195 setROCMisalignment(type, 0, sector, potentialMin, potentialMax);
3196}
3197
3198template <typename DataT>
3199void SpaceCharge<DataT>::setROCMisalignmentRotationAlongY(const int sector, const int type, const float potentialMin, const float potentialMax)
3200{
3201 setROCMisalignment(type, 1, sector, potentialMin, potentialMax);
3202}
3203
3204template <typename DataT>
3205void SpaceCharge<DataT>::setROCMisalignment(int stackType, int misalignmentType, int sector, const float potMin, const float potMax)
3206{
3207 initContainer(mPotential[Sector(sector).side()], true);
3208 const auto indPhiTopIROC = getPotentialBoundaryGEMFrameAlongPhiIndices(GEMstack::IROCgem, false, Side::A, false, true);
3209
3210 const auto rotationPoints = [](const int regStart, const int regEnd, int misalignmentType, const float potMax, const float potMin) {
3211 if (misalignmentType == 0) {
3212 const auto& mapper = o2::tpc::Mapper::instance();
3213 const float radStart = mapper.getPadRegionInfo(regStart).getRadiusFirstRow();
3214 const auto& padReg = mapper.getPadRegionInfo(regEnd);
3215 const float radEnd = padReg.getRadiusFirstRow() + padReg.getPadHeight() * padReg.getNumberOfPadRows() - radStart;
3216 const float rotationPoint = radStart + radEnd / 2;
3217 const float slope = (potMax - potMin) / radEnd;
3218 return std::pair<float, float>{rotationPoint, slope};
3219 } else if (misalignmentType == 1) {
3220 return std::pair<float, float>{0, (potMax - potMin) / 100};
3221 } else {
3222 return std::pair<float, float>{0, (potMax + potMin) / 2};
3223 }
3224 };
3225
3226 if (stackType == 0) {
3227 const auto deltaPotPar = rotationPoints(0, 3, misalignmentType, potMax, potMin);
3228 const auto indPhiBottomIROC = getPotentialBoundaryGEMFrameAlongPhiIndices(GEMstack::IROCgem, true, Side::A, false, true);
3229 fillROCMisalignment(indPhiTopIROC, indPhiBottomIROC, sector, misalignmentType, deltaPotPar);
3230 } else if (stackType == 1) {
3231 const auto deltaPotPar = rotationPoints(4, 9, misalignmentType, potMax, potMin);
3232 const auto indPhiTopOROC3 = getPotentialBoundaryGEMFrameAlongPhiIndices(GEMstack::OROC3gem, false, Side::A, false, true);
3233 fillROCMisalignment(indPhiTopOROC3, indPhiTopIROC, sector, misalignmentType, deltaPotPar);
3234 } else if (stackType == 2) {
3235 const auto deltaPotPar = rotationPoints(0, 9, misalignmentType, potMax, potMin);
3236 const auto indPhiBottomIROC = getPotentialBoundaryGEMFrameAlongPhiIndices(GEMstack::IROCgem, true, Side::A, false, true);
3237 const auto indPhiTopOROC3 = getPotentialBoundaryGEMFrameAlongPhiIndices(GEMstack::OROC3gem, false, Side::A, false, true);
3238 fillROCMisalignment(indPhiTopIROC, indPhiBottomIROC, sector, misalignmentType, deltaPotPar);
3239 fillROCMisalignment(indPhiTopOROC3, indPhiTopIROC, sector, misalignmentType, deltaPotPar);
3240 }
3241}
3242
3243template <typename DataT>
3244void SpaceCharge<DataT>::fillROCMisalignment(const std::vector<std::pair<size_t, float>>& indicesTop, const std::vector<std::pair<size_t, float>>& indicesBottom, int sector, int misalignmentType, const std::pair<float, float>& deltaPotPar)
3245{
3246 for (const auto& indexw : indicesTop) {
3247 const int index = indexw.first;
3248 const int iZ = DataContainer3D<float>::getIndexZ(index, getNZVertices(), getNRVertices(), getNPhiVertices());
3249 const int iRStart = DataContainer3D<float>::getIndexR(index, getNZVertices(), getNRVertices(), getNPhiVertices());
3250 const int iPhi = DataContainer3D<float>::getIndexPhi(index, getNZVertices(), getNRVertices(), getNPhiVertices());
3251
3252 const int sectorTmp = iPhi / (getNPhiVertices() / SECTORSPERSIDE) + ((sector >= SECTORSPERSIDE) ? SECTORSPERSIDE : 0);
3253 if ((sector != -1) && (sectorTmp != sector)) {
3254 continue;
3255 }
3256 const Sector sec(sectorTmp);
3257
3258 for (size_t iR = iRStart; iR > 0; --iR) {
3259 const size_t currInd = (iZ + getNZVertices() * (iR + iPhi * getNRVertices()));
3260 const bool foundVertexBottom = std::binary_search(indicesBottom.begin(), indicesBottom.end(), std::make_pair(currInd, 0.0f), [](const auto& a, const auto& b) { return (a.first < b.first); });
3261
3262 if (foundVertexBottom) {
3263 break;
3264 }
3265
3266 // get local coordinates
3267 const float rPos = getRVertex(iR, sec.side());
3268 const float phiPos = getPhiVertex(iPhi, sec.side());
3269 const float zPos = getZVertex(iZ, sec.side());
3270 const float x = getXFromPolar(rPos, phiPos);
3271 const float y = getYFromPolar(rPos, phiPos);
3272 const LocalPosition3D pos(x, y, zPos);
3273 const LocalPosition3D lPos = Mapper::GlobalToLocal(pos, sec);
3274 float deltaPot = 0;
3275 if (misalignmentType == 0) {
3276 deltaPot = (lPos.X() - deltaPotPar.first) * deltaPotPar.second;
3277 } else if (misalignmentType == 1) {
3278 deltaPot = lPos.Y() * deltaPotPar.second;
3279 } else {
3280 deltaPot = deltaPotPar.second;
3281 }
3282 mPotential[sec.side()](iZ, iR, iPhi) += deltaPot;
3283 }
3284 }
3285}
3286
3287template <typename DataT>
3289{
3290 mGlobalCorrdR[side] -= otherSC.mGlobalCorrdR[side];
3291 mGlobalCorrdZ[side] -= otherSC.mGlobalCorrdZ[side];
3292 mGlobalCorrdRPhi[side] -= otherSC.mGlobalCorrdRPhi[side];
3293}
3294
3295template <typename DataT>
3297{
3298 mGlobalDistdR[side] -= otherSC.mGlobalDistdR[side];
3299 mGlobalDistdZ[side] -= otherSC.mGlobalDistdZ[side];
3300 mGlobalDistdRPhi[side] -= otherSC.mGlobalDistdRPhi[side];
3301}
3302
3303template <typename DataT>
3305{
3306 mGlobalCorrdR[side] *= val;
3307 mGlobalCorrdZ[side] *= val;
3308 mGlobalCorrdRPhi[side] *= val;
3309}
3310
3311template <typename DataT>
3313{
3314 initContainer(mDensity[side], true);
3315 const int verticesPerSector = mParamGrid.NPhiVertices / SECTORSPERSIDE;
3316 for (unsigned int iR = 0; iR < mParamGrid.NRVertices; ++iR) {
3317 for (unsigned int iZ = 0; iZ < mParamGrid.NZVertices; ++iZ) {
3318 for (unsigned int iPhi = 0; iPhi <= (verticesPerSector / 2); ++iPhi) {
3319 float meanDensity = 0;
3320 for (int iter = 0; iter < 2; ++iter) {
3321 for (unsigned int sec = 0; sec < SECTORSPERSIDE; ++sec) {
3322 const int iPhiTmpA = iPhi + sec * verticesPerSector;
3323 const int iPhiTmpB = ((sec + 1) * verticesPerSector - iPhi) % mParamGrid.NPhiVertices;
3324 if (iter == 0) {
3325 meanDensity += mDensity[side](iZ, iR, iPhiTmpA);
3326 meanDensity += mDensity[side](iZ, iR, iPhiTmpB);
3327 } else {
3328 const float densMean = meanDensity / (2 * SECTORSPERSIDE);
3329 mDensity[side](iZ, iR, iPhiTmpA) = densMean;
3330 mDensity[side](iZ, iR, iPhiTmpB) = densMean;
3331 }
3332 }
3333 }
3334 }
3335 }
3336 }
3337}
3338
3339template <typename DataT>
3340void SpaceCharge<DataT>::scaleChargeDensitySector(const float scalingFactor, const Sector sector)
3341{
3342 const Side side = sector.side();
3343 initContainer(mDensity[side], true);
3344 const int verticesPerSector = mParamGrid.NPhiVertices / SECTORSPERSIDE;
3345 const int sectorInSide = sector % SECTORSPERSIDE;
3346 const int iPhiFirst = sectorInSide * verticesPerSector;
3347 const int iPhiLast = iPhiFirst + verticesPerSector;
3348 for (unsigned int iR = 0; iR < mParamGrid.NRVertices; ++iR) {
3349 for (unsigned int iZ = 0; iZ < mParamGrid.NZVertices; ++iZ) {
3350 for (unsigned int iPhi = iPhiFirst; iPhi < iPhiLast; ++iPhi) {
3351 mDensity[side](iZ, iR, iPhi) *= scalingFactor;
3352 }
3353 }
3354 }
3355}
3356
3357template <typename DataT>
3358void SpaceCharge<DataT>::scaleChargeDensityStack(const float scalingFactor, const Sector sector, const GEMstack stack)
3359{
3360 const Side side = sector.side();
3361 initContainer(mDensity[side], true);
3362 const int verticesPerSector = mParamGrid.NPhiVertices / SECTORSPERSIDE;
3363 const int sectorInSide = sector % SECTORSPERSIDE;
3364 const int iPhiFirst = sectorInSide * verticesPerSector;
3365 const int iPhiLast = iPhiFirst + verticesPerSector;
3366 for (unsigned int iR = 0; iR < mParamGrid.NRVertices; ++iR) {
3367 const DataT radius = getRVertex(iR, side);
3368 for (unsigned int iPhi = iPhiFirst; iPhi < iPhiLast; ++iPhi) {
3369 const DataT phi = getPhiVertex(iR, side);
3370 const GlobalPosition3D pos(getXFromPolar(radius, phi), getYFromPolar(radius, phi), ((side == Side::A) ? 10 : -10));
3371 const auto& mapper = o2::tpc::Mapper::instance();
3372 const o2::tpc::DigitPos digiPadPos = mapper.findDigitPosFromGlobalPosition(pos);
3373 if (digiPadPos.isValid() && digiPadPos.getCRU().gemStack() == stack) {
3374 for (unsigned int iZ = 0; iZ < mParamGrid.NZVertices; ++iZ) {
3375 mDensity[side](iZ, iR, iPhi) *= scalingFactor;
3376 }
3377 }
3378 }
3379 }
3380}
3381
3382template <typename DataT>
3384{
3385 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);
3386 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);
3387}
3388
3389template <typename DataT>
3390float SpaceCharge<DataT>::getDCAr(float tgl, const int nPoints, const float phi, float rStart, o2::utils::TreeStreamRedirector* pcstream) const
3391{
3392 const float rmin = getRMin(o2::tpc::Side::A);
3393 std::vector<float> dRphi;
3394 std::vector<float> r;
3395 dRphi.reserve(nPoints);
3396 r.reserve(nPoints);
3397 for (int i = 0; i < nPoints; ++i) {
3398 float radius = (rStart > 0) ? (rStart + i) : (rmin + i);
3399 float z = tgl * radius;
3400 DataT distZ = 0;
3401 DataT distR = 0;
3402 DataT distRPhi = 0;
3403 getDistortionsCyl(z, radius, phi, o2::tpc::Side::A, distZ, distR, distRPhi);
3404 dRphi.emplace_back(distRPhi);
3405 r.emplace_back(radius);
3406 }
3407
3408 TF1 fPol("pol2", "pol2", rmin, r.back());
3409 fPol.SetParameter(0, 0);
3410 fPol.SetParameter(1, 0);
3411 fPol.SetParameter(2, 0);
3412 TGraph gr(r.size(), r.data(), dRphi.data());
3413 gr.Fit(&fPol, "QNRC");
3414 float dca = fPol.Eval(0);
3415 if (pcstream) {
3416 std::vector<double> params{fPol.GetParameter(0), fPol.GetParameter(1), fPol.GetParameter(2)};
3417 std::vector<float> rInterpol;
3418 std::vector<float> dRPhiInterpol;
3419 std::vector<float> distanceInterpol;
3420
3421 for (int i = 0; i < 500; ++i) {
3422 float radius = rmin + float(i) / 10;
3423 rInterpol.emplace_back(radius);
3424 dRPhiInterpol.emplace_back(fPol.Eval(radius));
3425 distanceInterpol.emplace_back(std::sqrt(rInterpol.back() * rInterpol.back() + dRPhiInterpol.back() * dRPhiInterpol.back()));
3426 }
3427
3428 for (int i = -200; i < 200; ++i) {
3429 float radius = float(i) / 10;
3430 rInterpol.emplace_back(radius);
3431 dRPhiInterpol.emplace_back(fPol.Eval(radius));
3432 distanceInterpol.emplace_back(std::sqrt(rInterpol.back() * rInterpol.back() + dRPhiInterpol.back() * dRPhiInterpol.back()));
3433 }
3434 (*pcstream) << "tree"
3435 << "r=" << r
3436 << "dRphi=" << dRphi
3437 << "tgl=" << tgl
3438 << "phi=" << phi
3439 << "dca=" << dca
3440 << "rInterpol=" << rInterpol
3441 << "dRPhiInterpol=" << dRPhiInterpol
3442 << "distanceInterpol=" << distanceInterpol
3443 << "param=" << params
3444 << "\n";
3445 }
3446 return dca;
3447}
3448
3449template <typename DataT>
3450void SpaceCharge<DataT>::setPotential(int iz, int ir, int iphi, Side side, float val)
3451{
3452 initContainer(mPotential[side], true);
3453 mPotential[side](iz, ir, iphi) = val;
3454}
3455
3456template <typename DataT>
3457void SpaceCharge<DataT>::downSampleObject(const int nZNew, const int nRNew, const int nPhiNew)
3458{
3459 o2::tpc::SpaceCharge<DataT> scNew(getBField(), nZNew, nRNew, nPhiNew);
3460 for (int iside = 0; iside < 2; ++iside) {
3461 const o2::tpc::Side side = (iside == 0) ? o2::tpc::Side::A : o2::tpc::Side::C;
3462 const std::vector<std::reference_wrapper<const DataContainer>> dataRef{mLocalDistdR[iside], mLocalDistdZ[iside], mLocalDistdRPhi[iside], mLocalVecDistdR[iside], mLocalVecDistdZ[iside], mLocalVecDistdRPhi[iside], mLocalCorrdR[iside], mLocalCorrdZ[iside], mLocalCorrdRPhi[iside], mGlobalDistdR[iside], mGlobalDistdZ[iside], mGlobalDistdRPhi[iside], mGlobalCorrdR[iside], mGlobalCorrdZ[iside], mGlobalCorrdRPhi[iside], mDensity[iside], mPotential[iside], mElectricFieldEr[iside], mElectricFieldEz[iside], mElectricFieldEphi[iside]};
3463 const std::vector<std::reference_wrapper<DataContainer>> dataNew{scNew.mLocalDistdR[iside], scNew.mLocalDistdZ[iside], scNew.mLocalDistdRPhi[iside], scNew.mLocalVecDistdR[iside], scNew.mLocalVecDistdZ[iside], scNew.mLocalVecDistdRPhi[iside], scNew.mLocalCorrdR[iside], scNew.mLocalCorrdZ[iside], scNew.mLocalCorrdRPhi[iside], scNew.mGlobalDistdR[iside], scNew.mGlobalDistdZ[iside], scNew.mGlobalDistdRPhi[iside], scNew.mGlobalCorrdR[iside], scNew.mGlobalCorrdZ[iside], scNew.mGlobalCorrdRPhi[iside], scNew.mDensity[iside], scNew.mPotential[iside], scNew.mElectricFieldEr[iside], scNew.mElectricFieldEz[iside], scNew.mElectricFieldEphi[iside]};
3464 for (int i = 0; i < dataRef.size(); ++i) {
3465 const auto& objRef = dataRef[i].get();
3466 if (objRef.getNDataPoints()) {
3467 auto& objNew = dataNew[i].get();
3468 scNew.initContainer(objNew, true);
3469 objNew = objRef.convert(scNew.mGrid3D[iside], mGrid3D[iside], sNThreads);
3470 }
3471 }
3472 }
3473 *this = std::move(scNew);
3474}
3475
3476using DataTD = double;
3477template class o2::tpc::SpaceCharge<DataTD>;
3478
3483
3488template void O2TPCSpaceCharge3DCalcD::calcGlobalCorrections(const NumFieldsD&, const int);
3489template void O2TPCSpaceCharge3DCalcD::calcGlobalCorrections(const AnaFieldsD&, const int);
3491template void O2TPCSpaceCharge3DCalcD::calcGlobalDistortions(const NumFieldsD&, const int maxIterations);
3492template void O2TPCSpaceCharge3DCalcD::calcGlobalDistortions(const AnaFieldsD&, const int maxIterations);
3493template void O2TPCSpaceCharge3DCalcD::calcGlobalDistortions(const DistCorrInterpD&, const int maxIterations);
3494template void O2TPCSpaceCharge3DCalcD::setGlobalCorrectionsFromFile<double>(TFile&, const Side);
3495template void O2TPCSpaceCharge3DCalcD::setGlobalCorrectionsFromFile<float>(TFile&, const Side);
3496template void O2TPCSpaceCharge3DCalcD::setGlobalDistortionsFromFile<double>(TFile&, const Side);
3497template void O2TPCSpaceCharge3DCalcD::setGlobalDistortionsFromFile<float>(TFile&, const Side);
3498
3499using DataTF = float;
3500template class o2::tpc::SpaceCharge<DataTF>;
3501
3506
3511template void O2TPCSpaceCharge3DCalcF::calcGlobalCorrections(const NumFieldsF&, const int);
3512template void O2TPCSpaceCharge3DCalcF::calcGlobalCorrections(const AnaFieldsF&, const int);
3514template void O2TPCSpaceCharge3DCalcF::calcGlobalDistortions(const NumFieldsF&, const int maxIterations);
3515template void O2TPCSpaceCharge3DCalcF::calcGlobalDistortions(const AnaFieldsF&, const int maxIterations);
3516template void O2TPCSpaceCharge3DCalcF::calcGlobalDistortions(const DistCorrInterpF&, const int maxIterations);
3517template void O2TPCSpaceCharge3DCalcF::setGlobalCorrectionsFromFile<double>(TFile&, const Side);
3518template void O2TPCSpaceCharge3DCalcF::setGlobalCorrectionsFromFile<float>(TFile&, const Side);
3519template void O2TPCSpaceCharge3DCalcF::setGlobalDistortionsFromFile<double>(TFile&, const Side);
3520template void O2TPCSpaceCharge3DCalcF::setGlobalDistortionsFromFile<float>(TFile&, const Side);
General auxilliary methods.
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
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.
float zMin
bounded_vector< float > bins
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.
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 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 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 correctElectron(GlobalPosition3D &point)
void initBField(const int field)
DataT regulateR(const DataT posR, const Side side) const
set r coordinate between 'RMIN - 4 * GRIDSPACINGR' and 'RMAX + 2 * GRIDSPACINGR'. the r coordinate is...
size_t getPhiBinsGapFrame(const Side side) const
void setROCMisalignmentRotationAlongX(const int sector, const int type, const float potentialMin, const float potentialMax)
void addGlobalCorrections(const SpaceCharge< DataT > &otherSC, const Side side)
add global corrections from other space charge object
void setPotentialBoundaryInnerRadius(const std::function< DataT(DataT)> &potentialFunc, const Side side)
void 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)
bool checkGridFromFile(std::string_view file, std::string_view tree)
void setSimNSector(const int nSectors)
simulate N sectors
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)
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
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)
int dumpGlobalCorrections(std::string_view file, const Side side, std::string_view option) const
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 setGlobalDistortionsFromFile(std::string_view file, const Side side)
void calcLocalDistortionsCorrections(const Type type, const ElectricFields &formulaStruct)
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)
void addBoundaryPotential(const SpaceCharge< DataT > &other, const Side side, const float scaling=1)
void getCorrections(const DataT x, const DataT y, const DataT z, const Side side, DataT &corrX, DataT &corrY, DataT &corrZ) const
static void normalizeHistoQVEps0(TH3 &histoIonsPhiRZ)
void getDistortions(const DataT x, const DataT y, const DataT z, const Side side, DataT &distX, DataT &distY, DataT &distZ) const
void calcLocalDistortionsCorrectionsRK4(const Type type, const Side side)
void mirrorPotential(const Side sideRef, const Side sideMirrored)
static int getOMPMaxThreads()
int dumpAnalyticalCorrectionsDistortions(TFile &outf) const
static void unsetSimNSector()
unsetting simulation of one sector
DistCorrInterpolator< DataT > getLocalDistInterpolator(const Side side) const
void getLocalCorrectionsCyl(const DataT z, const DataT r, const DataT phi, const Side side, DataT &lcorrZ, DataT &lcorrR, DataT &lcorrRPhi) const
void fillChargeFromIDCs(std::vector< CalDet< float > > &idcZero, const CalDet< float > &mapIBF, const float ionDriftTimeMS=200, const bool normToPadArea=true)
void scaleChargeDensityStack(const float scalingFactor, const Sector sector, const GEMstack stack)
scaling the space-charge density for given stack
void calcGlobalDistortions(const Fields &formulaStruct, const int maxIterations=3 *sSteps *129)
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 getLocalDistortionsCyl(const DataT z, const DataT r, const DataT phi, const Side side, DataT &ldistZ, DataT &ldistR, DataT &ldistRPhi) const
void fillChargeDensityFromCalDet(const std::vector< CalDet< float > > &calSCDensity3D)
void setGlobalDistortions(const std::function< void(int sector, DataT gx, DataT gy, DataT gz, DataT &gCx, DataT &gCy, DataT &gCz)> &gDist, const Side side)
void scaleCorrections(const float scaleFac, const Side side)
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 calcEField(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
SpaceCharge()
constructor for empty object. Can be used when buffers are loaded from file
void setChargeDensityFromFormula(const AnalyticalFields< DataT > &formulaStruct)
void averageDensityPerSector(const Side side)
average the sc density across all sectors per side
DataT getDensityCyl(const DataT z, const DataT r, const DataT phi, const Side side) const
void getElectricFieldsCyl(const DataT z, const DataT r, const DataT phi, const Side side, DataT &eZ, DataT &eR, DataT &ePhi) const
float getDCAr(float tgl, const int nPoints, const float phi, float rStart=-1, o2::utils::TreeStreamRedirector *pcstream=nullptr) const
DistCorrInterpolator< DataT > getGlobalDistInterpolator(const Side side) const
void setROCMisalignmentShiftZ(const int sector, const int type, const float potential)
void calculateDistortionsCorrections(const o2::tpc::Side side, const bool calcVectors=false)
void calcGlobalCorrections(const Fields &formulaStruct, const int type=3)
void setGlobalCorrections(const std::function< void(int sector, DataT gx, DataT gy, DataT gz, DataT &gCx, DataT &gCy, DataT &gCz)> &gCorr, const Side side)
void calcLocalDistortionCorrectionVector(const ElectricFields &formulaStruct)
void setPotentialFromFormula(const AnalyticalFields< DataT > &formulaStruct)
void distortElectron(GlobalPosition3D &point, const SpaceCharge< DataT > *scSCale=nullptr, float scale=0) const
void setGlobalCorrectionsFromFile(std::string_view file, const Side side)
unsigned short getNZVertices() const
void resetBoundaryPotentialToZeroInRangeZ(float zMin, float zMax, const Side side)
setting the boundary potential to 0 for z<zMin and z>zMax
void downSampleObject(const int nZNew, const int nRNew, const int nPhiNew)
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
GLsizeiptr size
Definition glcorearb.h:659
GLuint index
Definition glcorearb.h:781
GLuint const GLchar * name
Definition glcorearb.h:781
GLuint GLuint GLfloat weight
Definition glcorearb.h:5477
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLint y
Definition glcorearb.h:270
GLint GLint GLsizei GLint GLenum GLenum type
Definition glcorearb.h:275
GLenum const GLfloat * params
Definition glcorearb.h:272
GLboolean * data
Definition glcorearb.h:298
GLsizei GLenum const void * indices
Definition glcorearb.h:400
typedef void(APIENTRYP PFNGLCULLFACEPROC)(GLenum mode)
GLint GLint bottom
Definition glcorearb.h:1979
GLuint GLfloat * val
Definition glcorearb.h:1582
GLboolean r
Definition glcorearb.h:1233
GLuint start
Definition glcorearb.h:469
GLboolean GLboolean GLboolean GLboolean a
Definition glcorearb.h:1233
GLdouble GLdouble GLdouble z
Definition glcorearb.h:843
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat t1
Definition glcorearb.h:5034
constexpr int LHCMaxBunches
constexpr double LHCOrbitMUS
void check(const std::vector< std::string > &arguments, const std::vector< ConfigParamSpec > &workflowOptions, const std::vector< DeviceSpec > &deviceSpecs, CheckMatrix &matrix)
void bringTo02PiGen(T &phi)
void bringTo02PiGen(float &phi)
Definition Utils.h:80
constexpr int LHCBCPERTIMEBIN
Definition Constants.h:38
Global TPC definitions and constants.
Definition SimTraits.h:168
GEMstack
TPC GEM stack types.
Definition Defs.h:53
@ IROCgem
Definition Defs.h:53
@ OROC1gem
Definition Defs.h:54
@ OROC3gem
Definition Defs.h:56
@ OROC2gem
Definition Defs.h:55
DataT getZVertex(const size_t indexZ, const o2::tpc::RegularGrid3D< DataT > &grid)
math_utils::Point2D< float > LocalPosition2D
Definition Defs.h:107
constexpr double PI
Definition Defs.h:43
DataT getRVertex(const size_t indexR, const o2::tpc::RegularGrid3D< DataT > &grid)
constexpr double TWOPI
Definition Defs.h:44
constexpr double SECPHIWIDTH
Definition Defs.h:45
constexpr unsigned char SECTORSPERSIDE
Definition Defs.h:40
DataT getPhiVertex(const size_t indexPhi, const o2::tpc::RegularGrid3D< DataT > &grid)
math_utils::Point3D< float > GlobalPosition3D
Definition Defs.h:108
constexpr unsigned char SIDES
Definition Defs.h:41
Side
TPC readout sidE.
Definition Defs.h:35
@ A
Definition Defs.h:35
@ C
Definition Defs.h:36
DataT sum(const Vector< DataT, N > &a)
Definition Vector.h:107
@ streamDistortionsSC
stream distortions applied in the TPC space-charge class (used for example in the tpc digitizer)
static size_t getIndexR(size_t index, const int nz, const int nr, const int nphi)
static size_t getIndexZ(size_t index, const int nz, const int nr, const int nphi)
static size_t getIndexPhi(size_t index, const int nz, const int nr, const int nphi)
static int normalizeGridToNSector
the grid in phi direction is squashed from 2 Pi to (2 Pi / SECTORSPERSIDE). This can used to get the ...
float ADCdynamicRange
Dynamic range of the ADC [mV].
float ADCsaturation
ADC saturation [ADC counts].
float ElectronCharge
Electron charge [C].
float ChipGain
Gain of the SAMPA [mV/fC] - may be either 20 or 30.
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()))