Skip to content

Commit fb5df5b

Browse files
TPC SCD: explicit spline knots for createFromTrackResiduals
The spline knots of the FastTransform built from a voxel map were fixed to a knot at every second voxel bin, counted from both ends. With a non-uniform y/x binning (e.g. bins refined at the sector edges and at the sector centre) this skips knots in the refined bins, where the map is steepest, and with an even number of bins the two central bins get no knot at all (48 y/x bins refined at the centre: the FastTransform deviates from the map by up to ~0.4 mm there). TPCFastSpaceChargeCorrectionHelper::setY2XKnots / setZ2XKnots take the knot positions explicitly, in the coordinate of the voxel binning (y/x scaled to -1..1, z/x to 0..1, as the bin boundaries given to TrackResiduals::setY2XBinning / setZ2XBinning). A row's spline spans its first to its last voxel bin centre, so these two are always knots; knots outside them are dropped with a warning, as are knots closer than half the narrowest voxel bin to their neighbouring knot (the knots are put on an integer grid with 1/10 of the smallest knot distance as step and the spline stores one table entry per grid step, so nearly coinciding knots would make the correction object arbitrarily large). A knot interval without any voxel centre is reported. Without explicit knots (default) the previous rule is used unchanged. TPCFastTransformInit.C / TPCFastTransformInitCPM.C: optional arguments y2xKnots / z2xKnots (comma-separated); if empty, the user info "y2xKnots" / "z2xKnots" of the input voxel tree is used, so a map can carry its knots next to its binning. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 824ca2b commit fb5df5b

4 files changed

Lines changed: 124 additions & 13 deletions

File tree

‎Detectors/TPC/calibration/include/TPCCalibration/TPCFastSpaceChargeCorrectionHelper.h‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,24 @@ class TPCFastSpaceChargeCorrectionHelper
7373
/// get the number of threads
7474
int getNthreads() const { return mNthreads; }
7575

76+
/// sets the y/x spline knots of createFromTrackResiduals, in the coordinate of the voxel binning (y/x scaled to -1..1, as the
77+
/// bin boundaries of TrackResiduals::setY2XBinning). The spline of a row spans its first to its last voxel bin centre: those two are
78+
/// always knots, knots outside them are dropped, as are knots closer than half the narrowest voxel bin to their neighbouring knot.
79+
/// Empty (default): a knot at every second voxel bin, counted from both ends.
80+
void setY2XKnots(const std::vector<double>& knots) { mY2XKnots = knots; }
81+
82+
/// get the y/x spline knots of createFromTrackResiduals (empty: the default knots)
83+
const std::vector<double>& getY2XKnots() const { return mY2XKnots; }
84+
85+
/// sets the z/x spline knots of createFromTrackResiduals, in the coordinate of the voxel binning (z/x scaled to 0..1, i.e. z/x divided by
86+
/// SpacePointsCalibConfParam::maxZ2X, as the bin boundaries of TrackResiduals::setZ2XBinning). As for y/x, the first and the last voxel
87+
/// bin centre are always knots, knots outside them are dropped, as are knots closer than half the narrowest voxel bin to their
88+
/// neighbouring knot. Empty (default): a knot at every second voxel bin, counted from both ends.
89+
void setZ2XKnots(const std::vector<double>& knots) { mZ2XKnots = knots; }
90+
91+
/// get the z/x spline knots of createFromTrackResiduals (empty: the default knots)
92+
const std::vector<double>& getZ2XKnots() const { return mZ2XKnots; }
93+
7694
/// _______________ Main functionality ________________________
7795

7896
/// creates TPCFastSpaceChargeCorrection object from a continious space charge correction in local coordinates
@@ -183,6 +201,8 @@ class TPCFastSpaceChargeCorrectionHelper
183201
static TPCFastSpaceChargeCorrectionHelper* sInstance; ///< singleton instance
184202
bool mIsInitialized = 0; ///< initialization flag
185203
int mNthreads = 1; ///< n of threads to use
204+
std::vector<double> mY2XKnots; ///< y/x spline knots of createFromTrackResiduals (empty: the default)
205+
std::vector<double> mZ2XKnots; ///< z/x spline knots of createFromTrackResiduals (empty: the default)
186206
TPCFastTransformGeo mGeo; ///< geometry parameters
187207

188208
TPCFastSpaceChargeCorrectionMap mCorrectionMap{0, 0};

‎Detectors/TPC/calibration/src/TPCFastSpaceChargeCorrectionHelper.cxx‎

Lines changed: 74 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "TPCBase/ParameterGas.h"
2222
#include "TPCBase/Sector.h"
2323
#include "SpacePoints/TrackResiduals.h"
24+
#include "SpacePoints/SpacePointsCalibConfParam.h"
2425
#include "DataFormatsTPC/Defs.h"
2526
#include "TPCFastTransform.h"
2627
#include "ChebyshevFit1D.h"
@@ -513,21 +514,83 @@ std::unique_ptr<o2::gpu::TPCFastSpaceChargeCorrection> TPCFastSpaceChargeCorrect
513514
knotsDouble[0].reserve(nY2Xbins);
514515
knotsDouble[1].reserve(nZ2Xbins);
515516

516-
// to get enouth measurements, make a spline knot at every second bin. Boundary bins are always included.
517-
518-
for (int i = 0, j = nY2Xbins - 1; i <= j; i += 2, j -= 2) {
519-
knotsDouble[0].push_back(trackResiduals.getY2X(0, i));
520-
if (j >= i + 1) {
521-
knotsDouble[0].push_back(trackResiduals.getY2X(0, j));
522-
}
517+
// the spline knots, in the coordinate of the knot calculation below (y/x of row 0, z/x): the bin centres and widths of the voxels
518+
std::vector<double> binCentres[2];
519+
std::vector<double> binWidths[2];
520+
for (int i = 0; i < nY2Xbins; i++) {
521+
binCentres[0].push_back(trackResiduals.getY2X(0, i));
522+
binWidths[0].push_back(1. / trackResiduals.getDY2XI(0, i));
523523
}
524+
for (int i = 0; i < nZ2Xbins; i++) {
525+
binCentres[1].push_back(trackResiduals.getZ2X(i));
526+
binWidths[1].push_back(trackResiduals.getDZ2X(i));
527+
}
528+
// explicit knots (setY2XKnots / setZ2XKnots) are given in the coordinate of the voxel binning: y/x scaled to -1..1, z/x scaled to 0..1
529+
const double knotScale[2] = {trackResiduals.getMaxY2X(0), o2::tpc::SpacePointsCalibConfParam::Instance().maxZ2X};
530+
const std::vector<double>* explicitKnots[2] = {&mY2XKnots, &mZ2XKnots};
524531

525-
for (int i = 0, j = nZ2Xbins - 1; i <= j; i += 2, j -= 2) {
526-
knotsDouble[1].push_back(trackResiduals.getZ2X(i));
527-
if (j >= i + 1) {
528-
knotsDouble[1].push_back(trackResiduals.getZ2X(j));
532+
for (int dim = 0; dim < 2; dim++) {
533+
const auto& centres = binCentres[dim];
534+
auto& knots = knotsDouble[dim];
535+
if (explicitKnots[dim]->empty()) {
536+
// default: to get enough measurements, a spline knot at every second bin, counted from both ends. Boundary bins are always included.
537+
for (int i = 0, j = int(centres.size()) - 1; i <= j; i += 2, j -= 2) {
538+
knots.push_back(centres[i]);
539+
if (j >= i + 1) {
540+
knots.push_back(centres[j]);
541+
}
542+
}
543+
continue;
544+
}
545+
// explicit knots: the spline of a row spans its first to its last bin centre, so those are knots and the others must lie between them.
546+
// A knot closer than half the narrowest voxel bin to its neighbouring knot is dropped: the knots are put on an integer grid with 1/10 of
547+
// the smallest knot distance as step, and the spline stores a table with one entry per grid step, so nearly coinciding knots would make
548+
// the correction object arbitrarily large
549+
const double first = centres.front();
550+
const double last = centres.back();
551+
const double eps = 1.e-6 * std::abs(last - first);
552+
const double minKnotDistance = 0.5 * *std::min_element(binWidths[dim].begin(), binWidths[dim].end());
553+
std::vector<double> innerKnots;
554+
int nOutside = 0;
555+
for (const double k : *explicitKnots[dim]) {
556+
const double u = k * knotScale[dim];
557+
if (u < first - eps || u > last + eps) {
558+
++nOutside;
559+
} else {
560+
innerKnots.push_back(u);
561+
}
562+
}
563+
std::sort(innerKnots.begin(), innerKnots.end());
564+
int nTooClose = 0;
565+
knots.push_back(first);
566+
for (const double u : innerKnots) {
567+
if (std::abs(u - knots.back()) <= eps || std::abs(u - last) <= eps) {
568+
continue; // a repeated knot, or the first / the last bin centre given explicitly
569+
}
570+
if (u - knots.back() < minKnotDistance || last - u < minKnotDistance) {
571+
++nTooClose;
572+
continue;
573+
}
574+
knots.push_back(u);
575+
}
576+
knots.push_back(last);
577+
if (nOutside > 0) {
578+
LOG(warning) << "fast space charge correction helper: " << nOutside << (dim == 0 ? " y/x" : " z/x") << " knots outside the first and the last bin centre dropped";
579+
}
580+
if (nTooClose > 0) {
581+
LOG(warning) << "fast space charge correction helper: " << nTooClose << (dim == 0 ? " y/x" : " z/x") << " knots closer than half the narrowest voxel bin ("
582+
<< minKnotDistance / knotScale[dim] << ") to the neighbouring knot dropped";
583+
}
584+
// a knot interval without a voxel centre (endpoints included) leaves the spline there without data
585+
for (size_t i = 1; i < knots.size(); i++) {
586+
const bool covered = std::any_of(centres.begin(), centres.end(), [&](double c) { return c >= knots[i - 1] - eps && c <= knots[i] + eps; });
587+
if (!covered) {
588+
LOG(warning) << "fast space charge correction helper: no " << (dim == 0 ? "y/x" : "z/x") << " voxel centre between the knots " << knots[i - 1] / knotScale[dim] << " and " << knots[i] / knotScale[dim];
589+
}
529590
}
530591
}
592+
LOG(info) << "fast space charge correction helper: " << knotsDouble[0].size() << " y/x knots for " << nY2Xbins << " bins (" << (mY2XKnots.empty() ? "default" : "explicit") << "), "
593+
<< knotsDouble[1].size() << " z/x knots for " << nZ2Xbins << " bins (" << (mZ2XKnots.empty() ? "default" : "explicit") << ")";
531594

532595
std::vector<int> knotsInt[2];
533596

‎GPU/TPCFastTransformation/macro/TPCFastTransformInit.C‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ using namespace o2::tpc;
4545
using namespace o2::gpu;
4646

4747
void TPCFastTransformInit(const char* fileName = "debugVoxRes.root", const char* fileNameInv = "debugVoxResInv.root",
48-
const char* outFileName = "TPCFastTransform_VoxRes.root", bool useSmoothed = false, bool invertSigns = false, bool doDebug = true)
48+
const char* outFileName = "TPCFastTransform_VoxRes.root", bool useSmoothed = false, bool invertSigns = false, bool doDebug = true,
49+
const char* y2xKnots = "", const char* z2xKnots = "")
4950
{
5051

5152
// Initialise TPCFastTransform object from "voxRes" tree of
@@ -119,6 +120,18 @@ void TPCFastTransformInit(const char* fileName = "debugVoxRes.root", const char*
119120
o2::tpc::TPCFastSpaceChargeCorrectionHelper* corrHelper = o2::tpc::TPCFastSpaceChargeCorrectionHelper::instance();
120121

121122
corrHelper->setNthreadsToMaximum();
123+
// the spline knots (setY2XKnots / setZ2XKnots, comma-separated, in the coordinate of the binning): the argument, else the user info
124+
// "y2xKnots" / "z2xKnots" of the map, else the helper's default (every second voxel bin)
125+
auto getKnots = [userInfo](const char* argument, const char* key) {
126+
std::string knots = argument ? argument : "";
127+
if (knots.empty() && userInfo->FindObject(key)) {
128+
knots = userInfo->FindObject(key)->GetTitle();
129+
}
130+
std::cout << key << ": " << (knots.empty() ? std::string("default (every second voxel bin)") : knots) << std::endl;
131+
return knots.empty() ? std::vector<double>{} : o2::RangeTokenizer::tokenize<double>(knots);
132+
};
133+
corrHelper->setY2XKnots(getKnots(y2xKnots, "y2xKnots"));
134+
corrHelper->setZ2XKnots(getKnots(z2xKnots, "z2xKnots"));
122135
// corrHelper->setNthreads(1);
123136

124137
if (debugMirrorAdata2C) {

‎GPU/TPCFastTransformation/macro/TPCFastTransformInitCPM.C‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ void TPCFastTransformInitCPM(const char* fileName = "debugVoxRes.root",
7272
float meanIDC = 0.f,
7373
float meanCTP = 0.f,
7474
int debug = 0,
75-
int nThreads = 8)
75+
int nThreads = 8,
76+
const char* y2xKnots = "",
77+
const char* z2xKnots = "")
7678
{
7779

7880
// Initialise TPCFastTransform object from "voxRes" tree of
@@ -156,6 +158,19 @@ void TPCFastTransformInitCPM(const char* fileName = "debugVoxRes.root",
156158
trackResiduals.setZ2XBinning(z2xBins);
157159
trackResiduals.init();
158160

161+
// the spline knots of the FT (TPCFastSpaceChargeCorrectionHelper::setY2XKnots / setZ2XKnots, comma-separated, in the coordinate of the
162+
// binning): the argument, else the user info "y2xKnots" / "z2xKnots" of the map, else the helper's default (every second voxel bin)
163+
auto getKnots = [userInfo](const char* argument, const char* key) {
164+
std::string knots = argument ? argument : "";
165+
if (knots.empty() && userInfo->FindObject(key)) {
166+
knots = userInfo->FindObject(key)->GetTitle();
167+
}
168+
LOGP(info, "{}: {}", key, knots.empty() ? std::string("default (every second voxel bin)") : knots);
169+
return knots.empty() ? std::vector<double>{} : o2::RangeTokenizer::tokenize<double>(knots);
170+
};
171+
o2::tpc::TPCFastSpaceChargeCorrectionHelper::instance()->setY2XKnots(getKnots(y2xKnots, "y2xKnots"));
172+
o2::tpc::TPCFastSpaceChargeCorrectionHelper::instance()->setZ2XKnots(getKnots(z2xKnots, "z2xKnots"));
173+
159174
auto getFromUserInfo = [userInfo](std::string value, float& valueF) {
160175
if (valueF != 0) {
161176
LOGP(info, "{} set to {} via command line, not reading it from userInfo", value, valueF);

0 commit comments

Comments
 (0)