BmnRoot 26.10.0-210-gc52d34b0
Software framework of the BM@N experiment
Loading...
Searching...
No Matches
vertex.C
Go to the documentation of this file.
1/*
2 BM@N alignment routine
3 BM@N experiment at NICA complex, JINR, 2026
4
5 Department: Math & Soft Group of HEP lab
6 Author: Claude (Opus 5)
7 Supervisor: Igor Polev, polev@jinr.int
8
9 Primary vertex study, phase 0 of the TRG expansion (see Expansion.md):
10 how well the beam line measured by SiBT predicts where the STS tracks start,
11 measured before any of it is built into the aligner
12*/
13
14// headers below are for IDE syntax to work and they will be
15// turned off during execution in ROOT macro mode
16#ifndef __CLING__
17#include "BmnAlignModels.h" // IWYU pragma: keep
18#include "BmnDataFilters.h"
19#include "BmnDataReader.h"
20#include "BmnHit.h"
21#include "BmnHitRecord.h"
22#include "BmnWeightedLine.h"
23#include "CbmStsHit.h"
24#include "CbmStsTrack.h"
25#include "TClonesArray.h"
26#include "TF1.h"
27#include "TFile.h"
28#include "TMath.h"
29#include "TProfile.h"
30#include "TTree.h"
31#include "TTreeReaderValue.h"
32
33#include <algorithm>
34#include <cmath>
35#include <iostream>
36#include <vector>
37#endif
38
39#include "fairlogger/Logger.h"
40
41/**
42 * @brief Compare the SiBT beam line with the STS tracks at the target centre.
43 *
44 * For every event with one hit in each SiBT station the beam is fitted as a
45 * straight line and taken at the target centre z_c. Every STS track the aligner
46 * would accept is fitted the way the aligner fits it - a weighted straight line
47 * per projection - and taken at the same z_c. The per-event ntuple is written
48 * out for interactive study, and a summary answers the phase 0 questions: the
49 * real beam error, where the tracks meet the beam and how far they scatter around
50 * it, the window that selects vertex tracks, and how many tracks of one event
51 * share its vertex hit (see Expansion.md).
52 *
53 * The residual is r = beam - track at z_c. A track from z_v = z_c + dz gives
54 * r = (track slope - beam slope) * dz. A z offset of the tracker relative to the
55 * target has the same form, so the dz fitted from the slope dependence is the
56 * vertex z in the tracker frame, not the interaction depth. Before the alignment
57 * the station halves are shifted against each other, so tracks going to either
58 * side of the beam get a line of their own.
59 *
60 * @param fileListPath text file listing the DST files, '#' lines are ignored
61 * @param outPath .root file for the ntuple and the profiles
62 * @param maxEvents number of events to read, 0 for all of them
63 *
64 * @return 0 on success, -1 on a setup failure
65 */
66int vertex(const char* fileListPath = "/home/igor/bmnroot/align_new/cfg/run9/9555.noa_rlerr.sample.list",
67 const char* outPath = "vertex.root",
68 Long64_t maxEvents = 1000)
69{
70 // the collision target installed in the run, as a FairRoot ASCII geometry file
71 constexpr const char* TARGET_GEO{"/home/igor/bmnroot/geometry/target_Sn.geo"};
72 constexpr Int_t SIBT_STATIONS{3}, SIBT_MIDDLE{1}; // stations are numbered along the beam
73 constexpr Double_t WINDOW_SIGMAS{5.0},
74 // this close to the beam direction in both projections a track
75 // does not point to the vertex: its residuals spread by centimetres
76 BEAM_LIKE_SLOPE{0.02};
77
78 fair::Logger::SetConsoleColor(true);
79 fair::Logger::SetConsoleSeverity("info");
80
81 BmnRun9DM detectorModel;
82 if (!detectorModel.Init(TARGET_GEO))
83 return -1;
84 const Double_t zStart{detectorModel.Target().zStart}, zEnd{detectorModel.Target().zEnd}, zc{0.5 * (zStart + zEnd)},
85 halfT{0.5 * (zEnd - zStart)};
86
87 BmnDataReader data{fileListPath};
88 TTreeReader& reader{data.Reader()};
89 TTreeReaderValue<TClonesArray> sibtHits{reader, "BmnSiBTHit"}, stsTracks{reader, "StsVector"},
90 stsHits{reader, "StsHit"};
91
92 TFile out{outPath, "RECREATE"};
93 if (out.IsZombie())
94 return -1;
95 // owned by the file, which deletes it on Close()
96 TTree* tree{new TTree("vertex", "SiBT beam line vs straight STS tracks at the target centre")};
97 Float_t beamX, beamY, beamErrX, beamErrY, beamSlopeX, beamSlopeY, beamPullX, beamPullY;
98 Int_t tracksAll;
99 std::vector<Float_t> slopeX, slopeY, resX, resY, errX, errY;
100 tree->Branch("beamX", &beamX);
101 tree->Branch("beamY", &beamY);
102 tree->Branch("beamErrX", &beamErrX);
103 tree->Branch("beamErrY", &beamErrY);
104 tree->Branch("beamSlopeX", &beamSlopeX);
105 tree->Branch("beamSlopeY", &beamSlopeY);
106 tree->Branch("beamPullX", &beamPullX);
107 tree->Branch("beamPullY", &beamPullY);
108 tree->Branch("tracksAll", &tracksAll);
109 tree->Branch("slopeX", &slopeX);
110 tree->Branch("slopeY", &slopeY);
111 tree->Branch("resX", &resX);
112 tree->Branch("resY", &resY);
113 tree->Branch("errX", &errX);
114 tree->Branch("errY", &errY);
115
116 Long64_t events{0}, eventsWithBeam{0}, tracksKept{0}, ndf{0};
117 Double_t chi2X{0.0}, chi2Y{0.0};
118 while ((maxEvents <= 0 || events < maxEvents) && reader.Next()) {
119 events++;
120 if (SIBT_STATIONS != sibtHits->GetEntriesFast())
121 continue;
122
123 BmnWeightedLine bx, by;
124 UInt_t stations{0};
125 Double_t midZ{0.0}, midX{0.0}, midY{0.0};
126 for (Int_t i{0}; i < SIBT_STATIONS; i++) {
127 const BmnHit& hit{*static_cast<BmnHit*>(sibtHits->At(i))};
128 if (hit.GetStation() >= 0 && hit.GetStation() < SIBT_STATIONS)
129 stations |= 1u << hit.GetStation();
130 if (SIBT_MIDDLE == hit.GetStation()) {
131 midZ = hit.GetZ();
132 midX = hit.GetX();
133 midY = hit.GetY();
134 }
135 bx.Add(hit.GetZ(), hit.GetX(), BmnHitRecord::Weight(hit.GetDx()));
136 by.Add(hit.GetZ(), hit.GetY(), BmnHitRecord::Weight(hit.GetDy()));
137 }
138 if (stations != (1u << SIBT_STATIONS) - 1)
139 continue;
140 eventsWithBeam++;
141
142 beamX = bx.At(zc);
143 beamY = by.At(zc);
144 beamErrX = std::sqrt(bx.Var(zc));
145 beamErrY = std::sqrt(by.Var(zc));
146 beamSlopeX = bx.Slope();
147 beamSlopeY = by.Slope();
148 // three points leave one degree of freedom: all residuals are proportional
149 // to a single pull, chi2 is its square, and the middle hit gives its sign
150 beamPullX = std::copysign(std::sqrt(std::max(bx.Chi2(), 0.0)), midX - bx.At(midZ));
151 beamPullY = std::copysign(std::sqrt(std::max(by.Chi2(), 0.0)), midY - by.At(midZ));
152 tracksAll = stsTracks->GetEntriesFast();
153 slopeX.clear();
154 slopeY.clear();
155 resX.clear();
156 resY.clear();
157 errX.clear();
158 errY.clear();
159
160 for (Int_t i{0}; i < tracksAll; i++) {
161 const CbmStsTrack& track{*static_cast<CbmStsTrack*>(stsTracks->At(i))};
162 BmnWeightedLine tx, ty;
163 for (Int_t h{0}; h < track.GetNStsHits(); h++) {
164 const CbmStsHit& hit{*static_cast<CbmStsHit*>(stsHits->At(track.GetStsHitIndex(h)))};
165 if (!BmnAcceptHit(hit, detectorModel))
166 continue;
167 tx.Add(hit.GetZ(), hit.GetX(), BmnHitRecord::Weight(hit.GetDx()));
168 ty.Add(hit.GetZ(), hit.GetY(), BmnHitRecord::Weight(hit.GetDy()));
169 }
170 if (!BmnAcceptTrack(track, tx.N()))
171 continue;
172
173 chi2X += tx.Chi2();
174 chi2Y += ty.Chi2();
175 ndf += tx.N() - 2;
176 slopeX.push_back(tx.Slope());
177 slopeY.push_back(ty.Slope());
178 resX.push_back(bx.At(zc) - tx.At(zc));
179 resY.push_back(by.At(zc) - ty.At(zc));
180 errX.push_back(std::sqrt(tx.Var(zc)));
181 errY.push_back(std::sqrt(ty.Var(zc)));
182 }
183 tracksKept += slopeX.size();
184 tree->Fill();
185 }
186
187 std::cout << TString::Format("\n%lld events read, %lld (%.1f%%) with one hit in each SiBT station\n"
188 "target z from %.4f to %.4f cm, residuals taken at z_c = %.4f cm\n"
189 "%lld tracks accepted (%s), %.1f per event with a beam line\n",
190 events, eventsWithBeam, 100.0 * eventsWithBeam / std::max(events, 1LL), zStart, zEnd,
191 zc, tracksKept, BMN_TRACKS_CUT_STR,
192 static_cast<Double_t>(tracksKept) / std::max(eventsWithBeam, 1LL));
193 if (!tracksKept)
194 return -1;
195
196 tree->SetEstimate(std::max(tracksKept, eventsWithBeam) + 1);
197 auto quantile = [tree](const TString& expr, const TString& cut, Double_t q) {
198 const Long64_t n{tree->Draw(expr, cut, "goff")};
199 return n > 0 ? TMath::KOrdStat(n, tree->GetV1(), static_cast<Long64_t>(q * (n - 1))) : 0.0;
200 };
201 const Double_t madToSigma{1.0 / TMath::NormQuantile(0.75)};
202
203 const TString vertexLike{TString::Format("(abs(slopeX - beamSlopeX) >= %g || abs(slopeY - beamSlopeY) >= %g)",
204 BEAM_LIKE_SLOPE, BEAM_LIKE_SLOPE)};
205 const Long64_t tracksVertexLike{tree->Draw("resX", vertexLike, "goff")};
206 TString bothWindows{vertexLike};
207
208 for (const TString p : {"X", "Y"}) {
209 const TString res{"res" + p}, slope{"(slope" + p + " - beamSlope" + p + ")"}, err{"err" + p},
210 beamErr{"beamErr" + p}, beamPull{"beamPull" + p};
211 const Double_t chi2ndf{(p == "X" ? chi2X : chi2Y) / ndf}, chi2Other{(p == "X" ? chi2Y : chi2X) / ndf};
212
213 // a fixed misalignment of the middle SiBT station shifts every pull alike,
214 // so the error scale is the spread around the median, not the width around zero
215 const Double_t pullOffset{quantile(beamPull, "", 0.5)},
216 beamScale{madToSigma * quantile(TString::Format("abs(%s - (%g))", beamPull.Data(), pullOffset), "", 0.5)};
217
218 // The target depth is a uniform spread: in the pull it enters as a variance,
219 // in the window as a hard edge, so the window does not widen with WINDOW_SIGMAS
220 // at large slopes. Secondaries are trimmed by shrinking the window step by step,
221 // and the lines are fitted as a mean inside the window, so they follow it.
222 const TString total{TString::Format("sqrt((%g * %s)^2 + %s^2)", beamScale, beamErr.Data(), err.Data())},
223 depth{TString::Format("abs%s * %g", slope.Data(), halfT)}, side[2]{slope + " < 0", slope + " >= 0"};
224 const Double_t slope99{quantile("abs" + slope, vertexLike, 0.99)};
225 // owned by the file, which deletes them on Close()
226 TProfile* const profile[2]{
227 new TProfile("vertexZ" + p + "neg", "slope < 0;track slope - beam slope;beam - track at z_c, cm", 20,
228 -slope99, 0.0),
229 new TProfile("vertexZ" + p + "pos", "slope > 0;track slope - beam slope;beam - track at z_c, cm", 20, 0.0,
230 slope99)};
231 Double_t offset[2]{0.0, 0.0}, dz[2]{0.0, 0.0}, scale{1.0};
232 Bool_t fitted[2]{kFALSE, kFALSE};
233 TString window{vertexLike}, fromLine;
234 for (Int_t i{0}; i < 4; i++) {
235 for (Int_t s{0}; s < 2; s++) {
236 tree->Draw(TString::Format("%s:%s>>%s", res.Data(), slope.Data(), profile[s]->GetName()),
237 window + " && " + side[s], "prof goff");
238 // a side without tracks keeps a zero line, which selects nothing
239 fitted[s] = profile[s]->GetEntries() > 0 && 0 == static_cast<Int_t>(profile[s]->Fit("pol1", "Q0"));
240 if (fitted[s]) {
241 offset[s] = profile[s]->GetFunction("pol1")->GetParameter(0);
242 dz[s] = profile[s]->GetFunction("pol1")->GetParameter(1);
243 }
244 }
245 fromLine = TString::Format("(%s - (%s) * ((%g) + (%g) * %s) - (%s) * ((%g) + (%g) * %s))", res.Data(),
246 side[0].Data(), offset[0], dz[0], slope.Data(), side[1].Data(), offset[1], dz[1],
247 slope.Data());
248 scale = madToSigma
249 * quantile(
250 TString::Format("abs%s / sqrt(%s^2 + (%s)^2 / 3)", fromLine.Data(), total.Data(), depth.Data()),
251 window, 0.5);
252 window = TString::Format("%s && abs%s < %g * %s + %s", vertexLike.Data(), fromLine.Data(),
253 WINDOW_SIGMAS * scale, total.Data(), depth.Data());
254 }
255 bothWindows += " && " + window;
256
257 Long64_t sideTracks[2]{}, sideInWindow[2]{};
258 Double_t sideWidth[2]{};
259 for (Int_t s{0}; s < 2; s++) {
260 sideTracks[s] = tree->Draw(res, vertexLike + " && " + side[s], "goff");
261 sideInWindow[s] = tree->Draw(res, window + " && " + side[s], "goff");
262 sideWidth[s] = madToSigma * quantile("abs" + fromLine, window + " && " + side[s], 0.5);
263 if (!fitted[s])
264 offset[s] = dz[s] = sideWidth[s] = TMath::QuietNaN();
265 }
266 const Long64_t tracksInWindow{tree->Draw(res, window, "goff")};
267 const Double_t totalMedian{quantile(total, window, 0.5)}, slopeMedian{quantile("abs" + slope, window, 0.5)},
268 slope90{quantile("abs" + slope, window, 0.9)};
269 const Long64_t thicknessWins{tree->Draw(
270 res, window + TString::Format(" && (%s)^2 / 3 > (%g * %s)^2", depth.Data(), beamScale, beamErr.Data()),
271 "goff")};
272
273 std::cout << TString::Format("\n--- projection %s\n", p.Data());
274 std::cout << TString::Format("straight line chi2/ndf over accepted tracks: %.2f\n", chi2ndf);
275 if (chi2ndf > 10.0 * chi2Other)
276 std::cout << " ! far above the other projection: check the field used in reco,\n"
277 " the hit errors and the misalignment before trusting the numbers below\n";
278 std::cout << TString::Format(
279 "beam at z_c: sigma %.4f cm predicted, pull scale %.2f (middle hit offset %.2f sigma)\n"
280 "beam - track at z_c = offset + dz * (track slope - beam slope), fitted per sign of the slope:\n"
281 " slope < 0 slope > 0\n"
282 " tracks %9lld %9lld\n"
283 " in the vertex window, %% %9.1f %9.1f\n"
284 " offset, cm %9.4f %9.4f\n"
285 " dz, cm %9.3f %9.3f\n"
286 " width around the line, cm %9.4f %9.4f\n"
287 "dz is the vertex z - z_c in the tracker frame: interaction depth plus the tracker z offset\n"
288 "vertex window: |r - line| < %g * %.2f * total error + |track slope - beam slope| * %.4f\n"
289 "total error median %.4f cm; |track slope - beam slope| in window: median %.3f, 90%% below %.3f\n"
290 "thickness term (t^2/12) above beam error: %.1f%% of tracks in window\n",
291 quantile(beamErr, "", 0.5), beamScale, pullOffset, sideTracks[0], sideTracks[1],
292 100.0 * sideInWindow[0] / std::max(sideTracks[0], 1LL),
293 100.0 * sideInWindow[1] / std::max(sideTracks[1], 1LL), offset[0], offset[1], dz[0], dz[1], sideWidth[0],
294 sideWidth[1], WINDOW_SIGMAS, scale, halfT, totalMedian, slopeMedian, slope90,
295 100.0 * thicknessWins / std::max(tracksInWindow, 1LL));
296 }
297
298 // one row per track in both windows, each carrying the count of such tracks in its event
299 const Long64_t tracksWithVertex{tree->Draw("Sum$(" + bothWindows + ")", bothWindows, "goff")};
300 std::cout << TString::Format(
301 "\n--- both projections\n"
302 "beam-like tracks (|track slope - beam slope| < %g in x and y), left out: %.1f%% of accepted\n"
303 "tracks in both vertex windows: %.1f%% of accepted, %.1f per event with a beam line\n"
304 "copies of the event's vertex hit, mean over the tracks that take it: %.1f\n",
305 BEAM_LIKE_SLOPE, 100.0 * (tracksKept - tracksVertexLike) / tracksKept, 100.0 * tracksWithVertex / tracksKept,
306 static_cast<Double_t>(tracksWithVertex) / eventsWithBeam,
307 tracksWithVertex > 0 ? TMath::Mean(tracksWithVertex, tree->GetV1()) : 0.0);
308
309 out.Write();
310 out.Close();
311 std::cout << "\n" << outPath << " : ntuple 'vertex' and the vertex z profiles\n";
312 return 0;
313}
Bool_t BmnAcceptHit(const CbmStsHit &hit, const BmnDetectorModel &model)
Bool_t BmnAcceptTrack(const CbmStsTrack &track, Int_t usableHits, Bool_t wholeTrackOnly=kTRUE)
constexpr const char * BMN_TRACKS_CUT_STR
int i
Definition P4_F32vec4.h:22
bool Bool_t
int Int_t
unsigned int UInt_t
float Float_t
constexpr Bool_t kFALSE
double Double_t
long long Long64_t
TTreeReader & Reader()
const Layer & Target() const
Bool_t Init(const char *targetGeoFile=nullptr)
static constexpr Double_t Weight(Double_t error) noexcept
Short_t GetStation() const
Definition BmnHit.h:35
Double_t Chi2() const noexcept
Double_t Var(Double_t z) const noexcept
Int_t N() const noexcept
Double_t At(Double_t z) const noexcept
void Add(Double_t z, Double_t v, Double_t w=1.0) noexcept
Double_t Slope() const noexcept
Int_t GetNStsHits() const
Accessors.
Definition CbmStsTrack.h:60
Int_t GetStsHitIndex(Int_t iHit) const
Definition CbmStsTrack.h:62
virtual Double_t GetParameter(const TString &name) const
Int_t Write(const char *name=nullptr, Int_t opt=0, Int_t bufsize=0) const override
void Close(Option_t *option="") override
virtual TFitResultPtr Fit(const char *formula, Option_t *option="", Option_t *goption="", Double_t xmin=0, Double_t xmax=0)
virtual Double_t GetEntries() const
virtual TF1 * GetFunction(const char *name) const
const char * GetName() const override
R__ALWAYS_INLINE Bool_t IsZombie() const
const char * Data() const
static TString Format(const char *fmt,...)
virtual Int_t Fill()
virtual void SetEstimate(Long64_t nentries=1000000)
virtual Long64_t Draw(const char *varexp, const char *selection, Option_t *option="", Long64_t nentries=kMaxEntries, Long64_t firstentry=0)
virtual Double_t * GetV1()
virtual Int_t Branch(const char *folder, Int_t bufsize=32000, Int_t splitlevel=99)
Element KOrdStat(Size n, const Element *a, Size k, Size *work=0)
Double_t QuietNaN()
Double_t NormQuantile(Double_t p)
Double_t Mean(Iterator first, Iterator last)
int vertex(const char *fileListPath="/home/igor/bmnroot/align_new/cfg/run9/9555.noa_rlerr.sample.list", const char *outPath="vertex.root", Long64_t maxEvents=1000)
Compare the SiBT beam line with the STS tracks at the target centre.
Definition vertex.C:66