BmnRoot 26.10.0-210-gc52d34b0
Software framework of the BM@N experiment
Loading...
Searching...
No Matches
mp2prep.C
Go to the documentation of this file.
1/*
2 BM@N alignment routine
3 BM@N experiment at NICA complex, JINR, 2025
4
5 Department: Math & Soft Group of HEP lab
6 Author: Igor Polev, polev@jinr.int
7
8 Millepede-II input generator (cross-check tool for BmnAligner)
9
10 Translates one BmnAligner task - measurement model, detector model,
11 constraints, presigmas, dataset - into the four files pede consumes:
12
13 mp2mille.bin binary records: one local fit (track) per record
14 mp2par.txt initial values and pre-sigmas of the global parameters
15 mp2con.txt linear equality constraints
16 mp2steer.txt steering file referencing the three above
17 mp2labels.txt label -> object/parameter map (for res2json.py only)
18
19 The task is hard-wired to BmnMeasureLin4 + BmnRun8TopDM (see run.sh); the
20 config file paths are arguments.
21*/
22
23#include "BmnAlignDefines.h"
24#include "BmnMeasureLin4.h"
25#include "BmnRootIterator.h"
26#include "BmnRun8TopDM.h"
27#include "BmnWeightedLine.h"
28#include "Mille.cc" // millepede-ii ships the C binary writer as source only
29#include "Mille.h"
30#include "fairlogger/Logger.h"
31
32#include <array>
33#include <cmath>
34#include <fstream>
35#include <map>
36#include <nlohmann/json.hpp>
37#include <string>
38#include <vector>
39
40/*
41 * Parametrization
42 * ---------------
43 * BmnMeasureLin4 leaves phi and theta without effect, so - unlike BmnAligner,
44 * whose parameter vector is fixed at 6 and which has to fix the two by
45 * constraints - they simply do not exist here: pede only knows the parameters
46 * that appear in the binary file. Anything addressing them in the config files
47 * is dropped, with a report.
48 *
49 * The two levels of BmnAlignHierarchy (38 elements, 11 station halves) are
50 * expressed the way Millepede does hierarchies: every measurement carries the
51 * derivative twice, once against its element's own parameter and once against
52 * the same parameter of the owning composite. That is exact for the four
53 * parameters kept here - the propagation Jacobian J of BmnAlignHierarchy is
54 * the identity for dX/dY/dZ/psi once the composite pivot sits on the beam
55 * axis, which is the pivot default the models use.
56 */
57namespace
58{
59
60constexpr Int_t kParams{4};
61// index of each kept parameter within the 6-slot SVectGL of the measure model
62constexpr std::array<Int_t, kParams> kSlot{idX, idY, idZ, iPsi};
63constexpr std::array<const char*, kParams> kName{"dX", "dY", "dZ", "psi"};
64
65// label = object number (1-based) * 10 + parameter slot (1-based), composites
66// shifted out of the elements' range so that a label stays readable by eye
67constexpr Int_t kStride{10}, kCompBase{100000};
68
69inline Int_t LeafLabel(Int_t leaf, Int_t p)
70{
71 return (leaf + 1) * kStride + p + 1;
72}
73inline Int_t CompLabel(Int_t comp, Int_t p)
74{
75 return kCompBase + ((comp + 1) * kStride + p + 1);
76}
77
78// -1 for phi and theta: not parameters of this task
79inline Int_t SlotByName(const std::string& name)
80{
81 for (Int_t p = 0; p < kParams; p++)
82 if (name == kName[p])
83 return p;
84 return -1;
85}
86
87struct HitRec
88{
89 Double_t x, y, z, wx, wy;
90 Int_t leaf;
91};
92
93// weighted straight-line fit, x(z) = alpha + beta * z, as in
94// BmnAligner::LinRegressionAlphaPart but on the nominal (unaligned) geometry
95Bool_t FitLine(const std::vector<HitRec>& hits, Bool_t inY, Double_t& alpha, Double_t& beta)
96{
97 BmnWeightedLine line;
98 for (const HitRec& h : hits)
99 line.Add(h.z, inY ? h.y : h.x, inY ? h.wy : h.wx);
100 constexpr Double_t SINGULAR_DET{1.0E-6}; // hits too close in z to fit a line through
101 if (std::abs(line.Det()) < SINGULAR_DET)
102 return kFALSE;
103 beta = line.Slope();
104 alpha = line.At0();
105 return kTRUE;
106}
107
108} // namespace
109
110int mp2prep(const char* fileListPath = "/home/igor/bmnroot/align_new/cfg/run8/7466.noa_rlerr.list",
111 const char* constraintsPath = "/home/igor/bmnroot/align_new/cfg/run8/constraints/cnstr_38e_lin4.json",
112 const char* presigmaPath = "/home/igor/bmnroot/align_new/cfg/run8/presigmas/psg_38e_lin4.json",
113 const char* outDir = "/home/igor/bmnroot/align_new/mp2/out",
114 Int_t maxTracks = 0) // 0 means no limit
115{
116 fair::Logger::SetConsoleColor(true);
117 fair::Logger::SetConsoleSeverity("info");
118
119 BmnMeasureLin4 measure;
120 BmnRun8TopDM model;
121 if (!model.Init())
122 return -1;
123 LOG(info) << " detector model : " << model.Description();
124 LOG(info) << " measure model : " << measure.Description();
125
126 // --- hierarchy: which composite owns which element ---------------------
127 const std::vector<BmnDetectorModel::Composite> composites{model.Composites()};
128 VecInt_t compOf(model.Alignables(), -1);
129 std::map<std::string, Int_t> compByName;
130 for (std::size_t c = 0; c < composites.size(); c++) {
131 compByName[composites[c].name] = c;
132 for (Int_t leaf : composites[c].leaves)
133 compOf[leaf] = c;
134 }
135 for (Int_t leaf = 0; leaf < model.Alignables(); leaf++)
136 if (compOf[leaf] < 0) {
137 LOG(error) << " <!> element " << leaf << " belongs to no composite";
138 return -1;
139 }
140 LOG(info) << " parametrization : " << model.Alignables() << " elements + " << composites.size()
141 << " composites, " << kParams
142 << " parameters each = " << (model.Alignables() + (Int_t)composites.size()) * kParams
143 << " global parameters";
144
145 const std::string out{outDir};
146
147 // --- labels map ---------------------------------------------------------
148 {
149 std::ofstream ofs(out + "/mp2labels.txt");
150 if (!ofs.good()) {
151 LOG(error) << " <!> cannot write into " << out << " (does it exist?)";
152 return -1;
153 }
154 ofs << "# label object param owner\n";
155 for (Int_t leaf = 0; leaf < model.Alignables(); leaf++)
156 for (Int_t p = 0; p < kParams; p++)
157 ofs << LeafLabel(leaf, p) << ' ' << model.EncodedID(leaf) << ' ' << kName[p] << ' '
158 << composites[compOf[leaf]].name << '\n';
159 for (std::size_t c = 0; c < composites.size(); c++)
160 for (Int_t p = 0; p < kParams; p++)
161 ofs << CompLabel(c, p) << ' ' << composites[c].name << ' ' << kName[p] << " -\n";
162 }
163
164 // --- presigmas -> parameter file ----------------------------------------
165 {
166 std::ifstream ifs(presigmaPath);
167 if (!ifs.good()) {
168 LOG(error) << " <!> failed to open presigmas file " << presigmaPath;
169 return -1;
170 }
171 nlohmann::json js;
172 ifs >> js;
173
174 std::array<Double_t, kParams> leafSigma{};
175 std::vector<std::array<Double_t, kParams>> compSigma(composites.size());
176 Int_t dropped{0};
177 for (const nlohmann::json& e : js) {
178 if (!e.contains("ParamName") || !e.contains("Sigma")) {
179 LOG(error) << " <!> presigma entry without ParamName/Sigma:"
180 " only the (ParamName[, NodeName], Sigma) form is supported";
181 return -1;
182 }
183 const Int_t p{SlotByName(e["ParamName"].get<std::string>())};
184 if (p < 0) {
185 dropped++;
186 continue;
187 } // phi/theta: not a parameter here
188 const Double_t sigma{e["Sigma"].get<Double_t>()};
189 if (!e.contains("NodeName")) {
190 leafSigma[p] = sigma; // blanket entry: every element
191 continue;
192 }
193 const std::string node{e["NodeName"].get<std::string>()};
194 const auto it{compByName.find(node)};
195 if (it == compByName.end()) {
196 LOG(error) << " <!> presigma NodeName '" << node << "' is not a composite of the model";
197 return -1;
198 }
199 compSigma[it->second][p] = sigma;
200 }
201 LOG_IF(warning, dropped) << " ! " << dropped << " presigma entrie(s) on phi/theta ignored";
202
203 std::ofstream ofs(out + "/mp2par.txt");
204 ofs << "* label initial value pre-sigma (0 = free)\n"
205 << "Parameter\n";
206 for (Int_t leaf = 0; leaf < model.Alignables(); leaf++)
207 for (Int_t p = 0; p < kParams; p++)
208 ofs << LeafLabel(leaf, p) << " 0.0 " << leafSigma[p] << '\n';
209 for (std::size_t c = 0; c < composites.size(); c++)
210 for (Int_t p = 0; p < kParams; p++)
211 ofs << CompLabel(c, p) << " 0.0 " << compSigma[c][p] << '\n';
212 LOG(info) << " " << out << "/mp2par.txt : pre-sigmas written";
213 }
214
215 // --- constraints --------------------------------------------------------
216 {
217 std::ifstream ifs(constraintsPath);
218 if (!ifs.good()) {
219 LOG(error) << " <!> failed to open constraints file " << constraintsPath;
220 return -1;
221 }
222 nlohmann::json js;
223 ifs >> js;
224
225 std::ofstream ofs(out + "/mp2con.txt");
226 Int_t written{0}, skipped{0};
227 for (const nlohmann::json& jc : js) {
228 std::map<Int_t, Double_t> coef;
229 Double_t rhs{0.0};
230 Bool_t alive{kTRUE};
231 std::string comment{jc.value("Comment", std::string{})};
232
233 for (const nlohmann::json& jco : jc["Coefficients"]) {
234 if (jco.contains("RHS")) {
235 rhs = jco["RHS"].get<Double_t>();
236 continue;
237 }
238 if (!jco.contains("ParamName") || !jco.contains("Value")) {
239 LOG(error) << " <!> constraint coefficient without ParamName/Value:"
240 " only the (ParamName, DetectorID|NodeName, Value)"
241 " form is supported";
242 return -1;
243 }
244 const Int_t p{SlotByName(jco["ParamName"].get<std::string>())};
245 if (p < 0) { // constrains phi or theta: nothing to constrain
246 alive = kFALSE;
247 break;
248 }
249 const Double_t value{jco["Value"].get<Double_t>()};
250 if (jco.contains("NodeName")) {
251 const std::string node{jco["NodeName"].get<std::string>()};
252 const auto it{compByName.find(node)};
253 if (it == compByName.end()) {
254 LOG(error) << " <!> constraint NodeName '" << node << "' is not a composite of the model";
255 return -1;
256 }
257 coef[CompLabel(it->second, p)] += value;
258 } else if (jco.contains("DetectorID")) {
259 const Int_t id{jco["DetectorID"].get<Int_t>()};
260 if (model.UnknownID(id)) {
261 LOG(error) << " <!> constraint DetectorID " << id << " is not an element of the model";
262 return -1;
263 }
264 const Int_t leaf{model.Idx(id)};
265 // A coefficient on an element constrains its *effective*
266 // correction, so it must reach the composite too - this is
267 // the J^T c_leaf term of BmnAlignHierarchy::PromoteConstraints,
268 // with J the identity (see the note on the parametrization).
269 coef[LeafLabel(leaf, p)] += value;
270 coef[CompLabel(compOf[leaf], p)] += value;
271 } else {
272 LOG(error) << " <!> constraint coefficient with neither NodeName"
273 " nor DetectorID";
274 return -1;
275 }
276 }
277 if (!alive || coef.empty()) {
278 skipped++;
279 LOG(info) << " skipped (phi/theta only): " << comment;
280 continue;
281 }
282 ofs << "\n* " << comment << "\nConstraint " << rhs << '\n';
283 for (const auto& [label, value] : coef)
284 ofs << " " << label << " " << value << '\n';
285 written++;
286 }
287 LOG(info) << " " << out << "/mp2con.txt : " << written << " constraint(s) written, " << skipped
288 << " skipped as meaningless for this model";
289 }
290
291 // --- steering -----------------------------------------------------------
292 {
293 std::ofstream ofs(out + "/mp2steer.txt");
294 ofs << "* Millepede-II steering, generated by mp2prep.C\n"
295 << "mp2par.txt\n"
296 << "mp2con.txt\n"
297 << "Cfiles\n"
298 << "mp2mille.bin\n"
299 << "\n"
300 << "* full inversion: 196 parameters, and it is the covariance matrix\n"
301 << "* that makes the result comparable with BmnAligner's reported errors\n"
302 << "method inversion 5 0.001\n"
303 << "end\n";
304 }
305
306 // --- binary records -----------------------------------------------------
307 Mille mille((out + "/mp2mille.bin").c_str());
308 BmnRootIterator itr(fileListPath, model, nullptr, kTRUE, maxTracks);
309 if (!itr.Initialized()) {
310 LOG(error) << " <!> failed to open the dataset " << fileListPath;
311 return -1;
312 }
313
314 std::vector<HitRec> hits;
315 Long64_t nTracks{0}, nHits{0}, nBadFit{0};
316 itr.ResetAll();
317 while (!itr.EndOfTracks()) {
318 hits.clear();
319 while (!itr.EndOfHits()) {
320 const Int_t detID{itr.HitDetectorID()};
321 if (model.UnknownID(detID)) {
322 LOG(error) << " <!> unknown detector module encoding " << detID;
323 return -1;
324 }
325 hits.push_back(HitRec{itr.HitX(), itr.HitY(), itr.HitZ(), itr.HitWx(), itr.HitWy(), model.Idx(detID)});
326 itr.NextHit();
327 }
328
329 // Global derivatives depend on the track parameters, so the record needs
330 // a reference trajectory. The measurements themselves go in unmodified:
331 // the model is linear in the local parameters, so pede refits them.
332 Double_t alphaX, betaX, alphaY, betaY;
333 if (!FitLine(hits, kFALSE, alphaX, betaX) || !FitLine(hits, kTRUE, alphaY, betaY)) {
334 nBadFit++;
335 itr.NextTrack();
336 continue;
337 }
338 const SVectLC lc{alphaX, betaX, alphaY, betaY};
339
340 for (const HitRec& h : hits) {
341 const SVectGL gx{measure.dXdA(h.z, lc)}, gy{measure.dYdA(h.z, lc)};
342 const Int_t comp{compOf[h.leaf]};
343
344 Int_t label[2 * kParams];
345 float derGl[2 * kParams];
346 for (Int_t p = 0; p < kParams; p++) {
347 label[p] = LeafLabel(h.leaf, p);
348 label[kParams + p] = CompLabel(comp, p);
349 }
350
351 float derLc[BMN_LOCAL_PARAMS_PT]{1.0f, (float)h.z, 0.0f, 0.0f};
352 for (Int_t p = 0; p < kParams; p++)
353 derGl[p] = derGl[kParams + p] = (float)gx(kSlot[p]);
354 mille.mille(BMN_LOCAL_PARAMS_PT, derLc, 2 * kParams, derGl, label, (float)h.x,
355 (float)(1.0 / std::sqrt(h.wx)));
356
357 derLc[0] = 0.0f;
358 derLc[1] = 0.0f;
359 derLc[2] = 1.0f;
360 derLc[3] = (float)h.z;
361 for (Int_t p = 0; p < kParams; p++)
362 derGl[p] = derGl[kParams + p] = (float)gy(kSlot[p]);
363 mille.mille(BMN_LOCAL_PARAMS_PT, derLc, 2 * kParams, derGl, label, (float)h.y,
364 (float)(1.0 / std::sqrt(h.wy)));
365 }
366 mille.end();
367
368 nHits += hits.size();
369 if (++nTracks % 500000 == 0)
370 LOG(info) << " " << nTracks << " tracks written...";
371 itr.NextTrack();
372 }
373
374 LOG(info) << " " << out << "/mp2mille.bin : " << nTracks << " records, " << nHits << " hits (" << 2 * nHits
375 << " measurements)";
376 LOG_IF(warning, nBadFit) << " ! " << nBadFit << " track(s) skipped: degenerate line fit";
377 LOG(state) << " < mp2prep : done, run pede from " << out;
378 return 0;
379}
@ idX
@ idZ
@ iPsi
@ idY
RM::SVector< Double_t, BMN_LOCAL_PARAMS_PT > SVectLC
RM::SVector< Double_t, BMN_GLOBAL_PARAMS_PD > SVectGL
std::vector< Int_t > VecInt_t
#define BMN_LOCAL_PARAMS_PT
bool Bool_t
int Int_t
constexpr Bool_t kFALSE
double Double_t
long long Long64_t
constexpr Bool_t kTRUE
Bool_t EndOfTracks() const noexcept
Bool_t EndOfHits() const noexcept
Bool_t Initialized() const noexcept
Int_t EncodedID(Int_t idx) const
Bool_t Init(const char *targetGeoFile=nullptr)
Bool_t UnknownID(Int_t hitID) const
const std::string & Description() const
Int_t Idx(Int_t ID) const
const char * Description() const noexcept override
SVectGL dYdA(Double_t z0, const SVectLC &lc) const noexcept override
SVectGL dXdA(Double_t z0, const SVectLC &lc) const noexcept override
virtual Double_t HitY() const override
virtual void ResetAll() override
virtual Double_t HitWy() const override
virtual Int_t HitDetectorID() const override
virtual Double_t HitZ() const override
virtual Double_t HitX() const override
virtual Double_t HitWx() const override
virtual void NextTrack() override
virtual void NextHit() override
virtual Int_t Alignables() const noexcept override
Definition BmnRun8DM.h:139
virtual std::vector< Composite > Composites() const override
Double_t Det() const noexcept
Double_t At0() const noexcept
void Add(Double_t z, Double_t v, Double_t w=1.0) noexcept
Double_t Slope() const noexcept
double beta(double x, double y)
int mp2prep(const char *fileListPath="/home/igor/bmnroot/align_new/cfg/run8/7466.noa_rlerr.list", const char *constraintsPath="/home/igor/bmnroot/align_new/cfg/run8/constraints/cnstr_38e_lin4.json", const char *presigmaPath="/home/igor/bmnroot/align_new/cfg/run8/presigmas/psg_38e_lin4.json", const char *outDir="/home/igor/bmnroot/align_new/mp2/out", Int_t maxTracks=0)
Definition mp2prep.C:110