GRSISort "v4.0.0.5"
An extension of the ROOT analysis Framework
Loading...
Searching...
No Matches
TLevelScheme.cxx
Go to the documentation of this file.
1#include "TLevelScheme.h"
2
3#if __cplusplus >= 201703L
4
5#include <iostream>
6#include <fstream>
7#include <sstream>
8#include <numeric>
9#include <algorithm>
10
11#include "TROOT.h"
12#include "TString.h"
13
14#include "Globals.h"
15#include "GCanvas.h"
16#include "TGRSIUtilities.h"
17
18float TGamma::fTextSize = 0.020;
19float TLevel::fTextSize = 0.025;
20
21std::vector<TLevelScheme*> TLevelScheme::fLevelSchemes;
22
23TGamma::TGamma(TLevelScheme* levelScheme, std::string label, const double& br, const double& ts)
24 : fBranchingRatio(br), fTransitionStrength(ts), fLabelText(std::move(label)), fLevelScheme(levelScheme)
25{
26 fLabelText.insert(0, 1, ' '); // prepend a space to get distance from arrow
27 SetArrowSize(0.01);
28}
29
30TGamma::TGamma(const TGamma& rhs)
31 : TArrow(rhs)
32{
33 if(fDebug || rhs.fDebug) {
34 std::cout << __PRETTY_FUNCTION__ << ": copying gamma \"" << rhs.fLabel << "\" to \"" << fLabel << "\" (" << &rhs << " to " << this << ")" << std::endl; // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
35 }
36
37 fDebug = rhs.fDebug;
38 fEnergy = rhs.fEnergy;
39 fEnergyUncertainty = rhs.fEnergyUncertainty;
40 fUseTransitionStrength = rhs.fUseTransitionStrength;
41 fScalingGain = rhs.fScalingGain;
42 fScalingOffset = rhs.fScalingOffset;
43 fBranchingRatio = rhs.fBranchingRatio;
44 fBranchingRatioUncertainty = rhs.fBranchingRatioUncertainty;
45 fBranchingRatioPercent = rhs.fBranchingRatioPercent;
46 fBranchingRatioPercentUncertainty = rhs.fBranchingRatioPercentUncertainty;
47 fTransitionStrength = rhs.fTransitionStrength;
48 fTransitionStrengthUncertainty = rhs.fTransitionStrengthUncertainty;
49 fLabelText = rhs.fLabelText;
50 fLabel = rhs.fLabel;
51 fLevelScheme = rhs.fLevelScheme;
52 fInitialEnergy = rhs.fInitialEnergy;
53 fFinalEnergy = rhs.fFinalEnergy;
54}
55
56TGamma& TGamma::operator=(const TGamma& rhs)
57{
58 if(fDebug || rhs.fDebug) {
59 std::cout << __PRETTY_FUNCTION__ << ": copying gamma \"" << rhs.fLabel << "\" to \"" << fLabel << "\" (" << &rhs << " to " << this << ")" << std::endl; // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
60 }
61
62 if(this != &rhs) {
63 fDebug = rhs.fDebug;
64 fEnergy = rhs.fEnergy;
65 fEnergyUncertainty = rhs.fEnergyUncertainty;
66 fUseTransitionStrength = rhs.fUseTransitionStrength;
67 fScalingGain = rhs.fScalingGain;
68 fScalingOffset = rhs.fScalingOffset;
69 fBranchingRatio = rhs.fBranchingRatio;
70 fBranchingRatioUncertainty = rhs.fBranchingRatioUncertainty;
71 fBranchingRatioPercent = rhs.fBranchingRatioPercent;
72 fBranchingRatioPercentUncertainty = rhs.fBranchingRatioPercentUncertainty;
73 fTransitionStrength = rhs.fTransitionStrength;
74 fTransitionStrengthUncertainty = rhs.fTransitionStrengthUncertainty;
75 fLabelText = rhs.fLabelText;
76 fLabel = rhs.fLabel;
77 fLevelScheme = rhs.fLevelScheme;
78 fInitialEnergy = rhs.fInitialEnergy;
79 fFinalEnergy = rhs.fFinalEnergy;
80 }
81
82 return *this;
83}
84
85void TGamma::UpdateWidth()
86{
87 if(fDebug) { std::cout << __PRETTY_FUNCTION__ << std::endl; } // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
88 auto arrowsize = static_cast<Width_t>((fUseTransitionStrength ? fTransitionStrength : fBranchingRatio) * fScalingGain + fScalingOffset);
89 SetLineWidth(arrowsize);
90}
91
92void TGamma::UpdateLabel()
93{
94 if(fDebug) { std::cout << __PRETTY_FUNCTION__ << std::endl; } // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
95 if(fLabel != nullptr) {
96 fLabel->SetText(fLabel->GetX(), fLabel->GetY(), fLabelText.c_str());
97 }
98}
99
100void TGamma::Draw(const double& x1, const double& y1, const double& x2, const double& y2)
101{
102 UpdateWidth();
103
104 SetX1(x1);
105 SetY1(y1);
106 SetX2(x2);
107 SetY2(y2);
108 TArrow::Draw("|>"); // |> means using a filled triangle as point at x2,y2
109 if(fLabel == nullptr) {
110 fLabel = new TLatex((x1 + x2) / 2., (y1 + y2) / 2., fLabelText.c_str());
111 } else {
112 fLabel->SetText((x1 + x2) / 2., (y1 + y2) / 2., fLabelText.c_str());
113 }
114 fLabel->SetTextColor(GetLineColor());
115 fLabel->SetTextAlign(12); // left and center adjusted
116 fLabel->SetTextFont(42); // helvetica-medium-r-normal (default is 62 = helvetica-bold-r-normal)
117 fLabel->SetTextSize(fTextSize); // text size in fraction of window width/height in pixel (whichever is smaller)
118 fLabel->Draw();
119 if(fDebug) { Print(); }
120}
121
122void TGamma::Print(Option_t*) const
123{
124 TArrow::Print();
125 std::cout << "Gamma with energy " << fEnergy << " +- " << fEnergyUncertainty << " (from " << fInitialEnergy << " to " << fFinalEnergy << ") " << (fUseTransitionStrength ? "using" : "not using") << " transition strength " << fTransitionStrength << ", branching " << fBranchingRatio << " = " << 100. * fBranchingRatioPercent << "%, scaling gain " << fScalingGain << ", scaling offset " << fScalingOffset << ", arrow size " << GetArrowSize() << ", line color " << GetLineColor() << ", line width " << GetLineWidth() << std::endl;
126}
127
128std::map<double, double> TGamma::CoincidentGammas()
129{
130 /// Returns a map with the energies and relative strength of all feeding and draining gamma rays in coincidence with this gamma ray.
131 if(fLevelScheme == nullptr) {
132 std::cerr << "Parent level scheme not set, can't find coincident gamma rays" << std::endl;
133 return {};
134 }
135 if(fDebug) {
136 std::cout << "Looking for coincident gammas for gamma of " << fEnergy << " kev from level at " << fInitialEnergy << " keV to level at " << fFinalEnergy << " keV" << std::endl;
137 }
138 fLevelScheme->ResetGammaMap(); // clear the map of feeding gammas before calling the recursive function using it
139 auto result = fLevelScheme->FeedingGammas(fInitialEnergy, fBranchingRatioPercent);
140 if(fDebug) {
141 std::cout << "Got " << result.size() << " gammas feeding level at " << fInitialEnergy << std::endl;
142 }
143 if(fFinalEnergy > 0) {
144 auto draining = fLevelScheme->DrainingGammas(fFinalEnergy);
145 if(fDebug) {
146 std::cout << "Plus " << draining.size() << " gammas draining level at " << fFinalEnergy << std::endl;
147 }
148 for(auto& [energy, factor] : draining) {
149 result[energy] += factor;
150 }
151 }
152 if(fDebug) {
153 std::cout << "Got " << result.size() << " coincident gammas:";
154 for(auto& element : result) {
155 std::cout << " " << element.first << " (" << element.second << "%)";
156 }
157 std::cout << std::endl;
158 }
159
160 return result;
161}
162
163void TGamma::PrintCoincidentGammas()
164{
165 auto map = CoincidentGammas();
166 std::cout << map.size() << " coincident gammas for gamma " << fEnergy << " +- " << fEnergyUncertainty << " (" << fInitialEnergy << " to " << fFinalEnergy << "):";
167 for(auto gamma : map) {
168 std::cout << " " << gamma.first << " (" << gamma.second << "%)";
169 }
170 std::cout << std::endl;
171}
172
173std::vector<std::tuple<double, std::vector<double>>> TGamma::ParallelGammas()
174{
175 /// Returns a map with the relative strength and energies of all gamma rays that connect the same initial and final level.
176 /// Meaning these are all gamma rays that are parallel with this one and together add up to the same energy.
177 if(fLevelScheme == nullptr) {
178 std::cerr << "Parent level scheme not set, can't find parallel gamma rays" << std::endl;
179 return {};
180 }
181 if(fDebug) {
182 std::cout << "Looking for parallel gammas for gamma of " << fEnergy << " kev from level at " << fInitialEnergy << " keV to level at " << fFinalEnergy << " keV" << std::endl;
183 }
184 auto result = fLevelScheme->ParallelGammas(fInitialEnergy, fFinalEnergy);
185 auto last = std::remove_if(result.begin(), result.end(), [](std::tuple<double, std::vector<double>> x) { return std::get<1>(x).size() < 2; });
186 if(fDebug) {
187 std::cout << "Removing " << std::distance(last, result.end()) << " paths, keeping " << std::distance(result.begin(), last) << std::endl;
188 }
189 result.erase(last, result.end());
190 // if(fDebug) {
191 // std::cout<<"Got "<<result.size()<<" paths from level at "<<fInitialEnergy<<" keV to level at "<<fFinalEnergy<<" keV:"<<std::endl;
192 // for(auto& element : result) {
193 // std::cout<<std::get<0>(element)<<":";
194 // double totalEnergy = 0.;
195 // for(auto& energy : std::get<1>(element)) {
196 // std::cout<<" "<<energy;
197 // totalEnergy += energy;
198 // }
199 // std::cout<<" ("<<totalEnergy<<")"<<std::endl;
200 // }
201 // }
202
203 return result;
204}
205
206void TGamma::PrintParallelGammas()
207{
208 auto vec = ParallelGammas();
209 std::cout << vec.size() << " parallel paths for gamma " << fEnergy << " +- " << fEnergyUncertainty << " (" << fInitialEnergy << " to " << fFinalEnergy << "):" << std::endl;
210 for(auto& element : vec) {
211 std::cout << std::get<0>(element) << ":";
212 double totalEnergy = 0.;
213 for(auto& energy : std::get<1>(element)) {
214 std::cout << " " << energy;
215 totalEnergy += energy;
216 }
217 std::cout << " (" << totalEnergy << ")" << std::endl;
218 }
219}
220
221TLevel::TLevel(TLevelScheme* levelScheme, const double& energy, const std::string& label)
222{
223 if(fDebug && levelScheme == nullptr) {
224 std::cout << "Warning, nullptr provided to new band \"" << label << "\" for parent level scheme, some functions might no be available" << std::endl;
225 }
226 fLevelScheme = levelScheme;
227 fEnergy = energy;
228 fLabel = label;
229}
230
231TLevel::TLevel(const TLevel& rhs)
232 : TPolyLine(rhs)
233{
234 if(fDebug || rhs.fDebug) {
235 std::cout << __PRETTY_FUNCTION__ << ": copying level \"" << rhs.fLabel << "\" to \"" << fLabel << "\" (" << &rhs << " to " << this << ")" << std::endl; // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
236 }
237
238 fDebug = rhs.fDebug;
239 fEnergy = rhs.fEnergy;
240 fEnergyUncertainty = rhs.fEnergyUncertainty;
241 fLabel = rhs.fLabel;
242 fGammas = rhs.fGammas;
243 fNofFeeding = rhs.fNofFeeding;
244 fLevelScheme = rhs.fLevelScheme;
245 fLevelLabel = rhs.fLevelLabel;
246 fEnergyLabel = rhs.fEnergyLabel;
247 fOffset = rhs.fOffset;
248 if(fDebug) {
249 std::cout << __PRETTY_FUNCTION__ << ": copied level \"" << rhs.fLabel << "\" to \"" << fLabel << "\" (" << &rhs << " to " << this << ")" << std::endl; // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
250 }
251}
252
253TLevel::~TLevel()
254{
255 if(fDebug) {
256 std::cout << "Deleting level \"" << fLabel << "\" at " << fEnergy << " keV (" << this << ")" << std::endl;
257 }
258}
259
260TLevel& TLevel::operator=(const TLevel& rhs)
261{
262 if(fDebug || rhs.fDebug) {
263 std::cout << __PRETTY_FUNCTION__ << ": copying level \"" << rhs.fLabel << "\" to \"" << fLabel << "\" (" << &rhs << " to " << this << ")" << std::endl; // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
264 }
265
266 if(this != &rhs) {
267 TPolyLine::operator=(rhs);
268 fDebug = rhs.fDebug;
269 fEnergy = rhs.fEnergy;
270 fLabel = rhs.fLabel;
271 fGammas = rhs.fGammas;
272 fNofFeeding = rhs.fNofFeeding;
273 fLevelScheme = rhs.fLevelScheme;
274 fLevelLabel = rhs.fLevelLabel;
275 fEnergyLabel = rhs.fEnergyLabel;
276 fOffset = rhs.fOffset;
277 if(fDebug) {
278 std::cout << __PRETTY_FUNCTION__ << ": copied level \"" << rhs.fLabel << "\" to \"" << fLabel << "\" (" << &rhs << " to " << this << ")" << std::endl; // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
279 }
280 }
281
282 return *this;
283}
284
285TGamma* TLevel::AddGamma(const double levelEnergy, const char* label, double br, double ts)
286{
287 /// MENU function to add gamma from this level to another level at "levelEnergy"
288 /// Adds a new gamma ray with specified branching ratio (default 100.), and transition strength (default 1.).
289 /// Returns gamma if it doesn't exist yet and was successfully added, null pointer otherwise.
290 return AddGamma(levelEnergy, 0., label, br, ts);
291}
292
293TGamma* TLevel::AddGamma(const double levelEnergy, const double energyUncertainty, const char* label, double br, double ts)
294{
295 /// Function to add gamma from this level to another level at "levelEnergy +- energyUncertainty".
296 /// Adds a new gamma ray with specified branching ratio (default 100.), and transition strength (default 1.).
297 /// Returns gamma if it doesn't exist yet and was successfully added, null pointer otherwise.
298 auto* level = fLevelScheme->FindLevel(levelEnergy, energyUncertainty);
299 if(level == nullptr) {
300 std::cerr << DRED << "Failed to find level at " << levelEnergy << " keV, can't add gamma!" << RESET_COLOR << std::endl;
301 return nullptr;
302 }
303
304 // now that we found a level, we will use its energy instead of the levelEnergy parameter
305 if(fGammas.count(level->Energy()) == 1) {
306 if(fDebug) {
307 std::cout << "Already found gamma ray from ";
308 Print();
309 std::cout << " to " << levelEnergy << "/" << level->Energy() << " keV";
310 level->Print();
311 }
312 return nullptr;
313 }
314
315 // create the gamma and set its properties
316 fGammas.emplace(std::piecewise_construct, std::forward_as_tuple(level->Energy()), std::forward_as_tuple(fLevelScheme, label, br, ts));
317 fGammas[level->Energy()].Debug(fDebug);
318 fGammas[level->Energy()].Energy(Energy() - levelEnergy); // here we do use the levelEnergy parameter instead of the energy of the level we found
319 fGammas[level->Energy()].EnergyUncertainty(energyUncertainty);
320 fGammas[level->Energy()].InitialEnergy(fEnergy);
321 fGammas[level->Energy()].FinalEnergy(level->Energy());
322 level->AddFeeding();
323 // re-calculate the branching ratios in % for all gammas
324 double sum = std::accumulate(fGammas.begin(), fGammas.end(), 0., [](double r, std::pair<const double, TGamma>& g) { r += g.second.BranchingRatio(); return r; });
325 for(auto& [en, gamma] : fGammas) {
326 gamma.BranchingRatioPercent(gamma.BranchingRatio() / sum);
327 gamma.BranchingRatioPercentUncertainty(gamma.BranchingRatioUncertainty() / sum);
328 }
329
330 if(fDebug) { Print(); }
331
332 fLevelScheme->Refresh();
333
334 return &(fGammas[level->Energy()]);
335}
336
337void TLevel::MoveToBand(const char* val)
338{
339 if(fLevelScheme == nullptr) {
340 std::cerr << __PRETTY_FUNCTION__ << ": Parent level scheme pointer not set!" << std::endl; // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
341 return;
342 }
343 if(fDebug) {
344 std::cout << "Trying to move level " << this << " at " << Energy() << " keV to band \"" << val << "\" using " << fLevelScheme << std::endl;
345 }
346 fLevelScheme->MoveToBand(val, this);
347}
348
349std::pair<double, double> TLevel::GetMinMaxGamma() const
350{
351 if(fGammas.empty()) { return std::make_pair(-1., -1.); }
352 auto result = std::make_pair(fGammas.begin()->second.Width(), fGammas.begin()->second.Width());
353 for(const auto& [energy, gamma] : fGammas) {
354 if(gamma.Width() < result.first) { result.first = gamma.Width(); }
355 if(gamma.Width() > result.second) { result.second = gamma.Width(); }
356 }
357
358 return result;
359}
360
361void TLevel::Draw(const double& left, const double& right)
362{
363 std::vector<double> x;
364 std::vector<double> y;
365 SetLineWidth(2.);
366 double tickLength = (fOffset == 0. ? 0. : 10.); // No need for a tick if we don't have an offset. maybe make this a parameter instead of hard-coded?
367 if(fOffset != 0.) {
368 // add extra point from label to edge of level
369 x.push_back(left);
370 y.push_back(fEnergy + fOffset);
371 x.push_back(left + tickLength);
372 y.push_back(fEnergy + fOffset);
373 x.push_back(left + tickLength + std::fabs(fOffset) / 2.);
374 y.push_back(fEnergy);
375 if(fDebug) { std::cout << "Non-zero offset " << fOffset << " => left line " << left << ", " << fEnergy + fOffset << " via " << left + tickLength << ", " << fEnergy + fOffset << " to " << left + tickLength + std::fabs(fOffset) / 2. << ", " << fEnergy << std::endl; }
376 }
377 x.push_back(left + tickLength + std::fabs(fOffset) / 2.);
378 y.push_back(fEnergy);
379 x.push_back(right - tickLength - std::fabs(fOffset) / 2.);
380 y.push_back(fEnergy);
381 if(fOffset != 0.) {
382 x.push_back(right - tickLength - std::fabs(fOffset) / 2.);
383 y.push_back(fEnergy);
384 x.push_back(right - tickLength);
385 y.push_back(fEnergy + fOffset);
386 x.push_back(right);
387 y.push_back(fEnergy + fOffset);
388 if(fDebug) { std::cout << "Non-zero offset " << fOffset << " => right line " << right - tickLength - std::fabs(fOffset) / 2. << ", " << fEnergy << " via " << right - tickLength << ", " << fEnergy + fOffset << " to " << right << ", " << fEnergy + fOffset << std::endl; }
389 }
390 SetPolyLine(x.size(), x.data(), y.data());
391 TPolyLine::Draw();
392 if(fDebug) { std::cout << "Drew TPolyLine using x " << left << "-" << right << " and y " << fEnergy << " with a width of " << GetLineWidth() << " and offset " << fOffset << std::endl; }
393}
394
395double TLevel::DrawLabel(const double& pos)
396{
397 if(fLevelLabel == nullptr) {
398 fLevelLabel = new TLatex(pos, fEnergy + fOffset, fLabel.c_str());
399 } else {
400 fLevelLabel->SetText(pos, fEnergy + fOffset, fLabel.c_str());
401 }
402 fLevelLabel->SetTextColor(GetLineColor());
403 fLevelLabel->SetTextAlign(12); // left adjusted and vertically centered
404 fLevelLabel->SetTextFont(42); // helvetica-medium-r-normal (default is 62 = helvetica-bold-r-normal)
405 fLevelLabel->SetTextSize(fTextSize); // text size in fraction of window width/height in pixel (whichever is smaller)
406 fLevelLabel->Draw();
407 return fLevelLabel->GetXsize();
408}
409
410double TLevel::DrawEnergy(const double& pos)
411{
412 if(fEnergyLabel == nullptr) {
413 fEnergyLabel = new TLatex(pos, fEnergy + fOffset, Form("%.0f keV", fEnergy));
414 } else {
415 fEnergyLabel->SetText(pos, fEnergy + fOffset, Form("%.0f keV", fEnergy));
416 }
417 fEnergyLabel->SetTextColor(GetLineColor());
418 fEnergyLabel->SetTextAlign(32); // right adjusted and vertically centered
419 fEnergyLabel->SetTextFont(42); // helvetica-medium-r-normal (default is 62 = helvetica-bold-r-normal)
420 fEnergyLabel->SetTextSize(fTextSize); // text size in fraction of window width/height in pixel (whichever is smaller)
421 fEnergyLabel->Draw();
422 return fEnergyLabel->GetXsize();
423}
424
425void TLevel::Print(Option_t*) const
426{
427 std::cout << "Level \"" << fLabel << "\" (" << this << ") at " << fEnergy << " keV has " << fGammas.size() << " draining gammas and " << fNofFeeding << " feeding gammas, debugging" << (fDebug ? "" : " not") << " enabled" << std::endl;
428 if(fDebug) {
429 for(const auto& [level, gamma] : fGammas) {
430 std::cout << "gamma to level " << level << " \"" << gamma.LabelText() << "\"" << std::endl;
431 }
432 }
433}
434
435TBand::TBand(TLevelScheme* levelScheme, const std::string& label)
436{
437 if(levelScheme == nullptr) {
438 std::cout << "Warning, nullptr provided to new band \"" << label << "\" for parent level scheme, some functions might no be available" << std::endl;
439 }
440 fLevelScheme = levelScheme;
441 SetLabel(label.c_str());
442 SetTextSize(0); // default size (?)
443 SetTextAlign(22); // centered in x and y
444}
445
446TBand::TBand(const TBand& rhs)
447 : TPaveLabel(rhs)
448{
449 if(fDebug || rhs.fDebug) {
450 std::cout << __PRETTY_FUNCTION__ << ": copying band \"" << rhs.GetLabel() << "\" to \"" << GetLabel() << "\" (" << &rhs << " to " << this << "), " << rhs.fLevels.size() << " level(s) to " << fLevels.size() << " level(s)" << std::endl; // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
451 }
452
453 fDebug = rhs.fDebug;
454 fLevels = rhs.fLevels;
455 fLevelScheme = rhs.fLevelScheme;
456
457 if(fDebug || rhs.fDebug) {
458 std::cout << __PRETTY_FUNCTION__ << ": copied band \"" << rhs.GetLabel() << "\" to \"" << GetLabel() << "\" (" << &rhs << " to " << this << "), " << rhs.fLevels.size() << " level(s) to " << fLevels.size() << " level(s)" << std::endl; // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
459 }
460}
461
462TBand& TBand::operator=(const TBand& rhs)
463{
464 if(this != &rhs) {
465 TPaveLabel::operator=(rhs);
466 if(fDebug || rhs.fDebug) {
467 std::cout << __PRETTY_FUNCTION__ << ": copying band \"" << rhs.GetLabel() << "\" to \"" << GetLabel() << "\" (" << &rhs << " to " << this << "), " << rhs.fLevels.size() << " level(s) to " << fLevels.size() << " level(s)" << std::endl; // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
468 }
469 fDebug = rhs.fDebug;
470 fLevels = rhs.fLevels;
471 fLevelScheme = rhs.fLevelScheme;
472 if(fDebug || rhs.fDebug) {
473 std::cout << __PRETTY_FUNCTION__ << ": copied band \"" << rhs.GetLabel() << "\" to \"" << GetLabel() << "\" (" << &rhs << " to " << this << "), " << rhs.fLevels.size() << " level(s) to " << fLevels.size() << " level(s)" << std::endl; // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
474 }
475 }
476
477 return *this;
478}
479
480TLevel* TBand::GetLevel(double energy)
481{
482 /// Returns level at provided energy, returns null pointer if level at this energy does not exist
483 if(fLevels.count(energy) == 0) {
484 std::cout << this << ": failed to find level with " << energy << " keV" << std::endl;
485 Print();
486 return nullptr;
487 }
488 if(fDebug) {
489 std::cout << this << ": found ";
490 fLevels.find(energy)->second.Print();
491 std::cout << "energy " << energy << " - " << &(fLevels.find(energy)->second) << std::endl;
492 }
493 return &(fLevels.find(energy)->second);
494}
495
496TLevel* TBand::FindLevel(double energy, double energyUncertainty)
497{
498 /// Returns level at the provided energy +- the uncertainty. If there isn't any level in that range a null pointer is returned.
499 /// If there are multiple levels in the range the one with the smallest energy difference is returned.
500 auto low = fLevels.lower_bound(energy - energyUncertainty);
501 auto high = fLevels.upper_bound(energy + energyUncertainty);
502 if(low == high) {
503 std::cout << this << ": failed to find level with " << energy << " +- " << energyUncertainty << " keV" << std::endl;
504 Print();
505 return nullptr;
506 }
507 if(std::distance(low, high) == 1) {
508 if(fDebug) {
509 std::cout << this << ": found ";
510 low->second.Print();
511 std::cout << "energy " << energy << " +- " << energyUncertainty << " - " << &(low->second) << std::endl;
512 }
513 return &(low->second);
514 }
515 // found multiple matching levels, use the one with the smallest energy difference
516 double en = low->first;
517 TLevel* level = &(low->second);
518 for(auto& it = ++low; it != high; ++it) {
519 if(std::fabs(energy - en) > std::fabs(energy - it->first)) {
520 en = it->first;
521 level = &(it->second);
522 }
523 }
524 if(fDebug) {
525 std::cout << this << ": found " << std::distance(low, high) << " levels, closest is";
526 level->Print();
527 std::cout << "energy " << energy << " +- " << energyUncertainty << " - " << level << std::endl;
528 }
529 return level;
530}
531
532std::pair<double, double> TBand::GetMinMaxGamma() const
533{
534 if(fLevels.empty()) { throw std::runtime_error("Trying to get min/max gamma width from empty band"); }
535 auto result = fLevels.begin()->second.GetMinMaxGamma();
536 for(const auto& [energy, level] : fLevels) {
537 auto [min, max] = level.GetMinMaxGamma();
538 if(min < result.first) { result.first = min; }
539 if(max > result.second) { result.second = max; }
540 }
541
542 return result;
543}
544
545TLevel* TBand::AddLevel(const double energy, const std::string& label)
546{
547 /// Add a new level with specified energy and label.
548 /// Returns pointer to new level, or, if it already exists, returns pointer to existing level.
549 /// Can be called from context menu.
550 // emplace returns a pair: the iterator of the newly created/existing element and a boolean true/false
551 if(fDebug) {
552 std::cout << __PRETTY_FUNCTION__ << " - " << this << ": Trying to add new level \"" << label << "\" at " << energy << " keV to " << std::flush; // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
553 Print();
554 }
555 auto [newLevel, success] = fLevels.emplace(std::piecewise_construct, std::forward_as_tuple(energy), std::forward_as_tuple(fLevelScheme, energy, label));
556 if(!success) {
557 std::cerr << DRED << "Failed to add new level \"" << label << "\" at " << energy << " keV to " << RESET_COLOR;
558 Print();
559 }
560
561 newLevel->second.Debug(fDebug);
562 if(fDebug) {
563 std::cout << this << ": adding new level \"" << label << "\" at " << energy << " keV " << (success ? "was a success" : "failed") << " now got " << fLevels.size() << "/" << NofLevels() << " levels, debugging" << (fDebug ? "" : " not") << " enabled" << std::endl;
564 if(success) {
565 std::cout << "New level: ";
566 newLevel->second.Print();
567 Print();
568 }
569 }
570
571 fLevelScheme->Refresh();
572
573 return &(newLevel->second);
574}
575
576void TBand::AddLevel(TLevel* level)
577{
578 if(fDebug) {
579 std::cout << __PRETTY_FUNCTION__ << " - " << this << " - \"" << GetLabel() << "\": Trying to add new level \"" << level->Label() << "\" at " << level->Energy() << " keV" << std::endl; // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
580 }
581 auto [iterator, success] = fLevels.insert(std::pair{level->Energy(), *level});
582 if(!success) {
583 std::cout << "Failed to insert level at " << level->Energy() << " into ";
584 Print();
585 } else if(fDebug) {
586 std::cout << "Successfully added new level \"" << level->Label() << "\" at " << level->Energy() << " keV" << std::endl;
587 Print();
588 }
589}
590
591void TBand::RemoveLevel(TLevel* level)
592{
593 /// Removes level that matches the energy of the provided level (if one exists).
594 auto nofRemoved = fLevels.erase(level->Energy());
595 if(fDebug) {
596 std::cout << "\"" << GetLabel() << "\": Removed " << nofRemoved << " levels" << std::endl;
597 Print();
598 }
599}
600
601double TBand::Width(double distance) const
602{
603 size_t nofGammas = 0.;
604 if(fDebug) {
605 std::cout << " (" << GetLabel() << " - " << fLevels.size() << ": ";
606 }
607 for(const auto& level : fLevels) {
608 nofGammas += level.second.NofDrainingGammas() + 1; // plus 1 for the gap between the gammas from each level
609 if(fDebug) {
610 std::cout << " " << nofGammas << " ";
611 }
612 }
613 if(nofGammas == 0) { nofGammas = 1; } // to get a minimum width
614
615 if(fDebug) {
616 std::cout << "=> width " << static_cast<double>(nofGammas) * distance << ") ";
617 }
618
619 return static_cast<double>(nofGammas) * distance;
620}
621
622void TBand::Print(Option_t*) const
623{
624 std::cout << this << ": band \"" << GetLabel() << "\" " << fLevels.size() << " level(s):";
625 for(const auto& level : fLevels) {
626 std::cout << " " << level.first;
627 }
628 std::cout << ", debugging" << (fDebug ? "" : " not") << " enabled" << std::endl;
629 if(fDebug) {
630 for(const auto& level : fLevels) {
631 level.second.Print();
632 }
633 }
634}
635
636TLevelScheme::TLevelScheme(const std::string& filename, bool debug)
637 : fDebug(debug)
638{
639 SetName("TLevelScheme");
640 SetLabel("Level Scheme");
641
642 // open the file and read the level scheme
643 // still need to decide what format that should be
644 if(!filename.empty()) {
645 if(!FileExists(filename.c_str())) {
646 std::cout << "file " << filename << " does not exist or we don't have read permissions" << std::endl;
647 } else {
648 auto lastDot = filename.find_last_of('.');
649 if(lastDot == std::string::npos) {
650 std::cout << "Failed to find extension => unknown data format" << std::endl;
651 } else {
652 std::string ext = filename.substr(lastDot + 1);
653 if(ext == "ensdf") {
654 ParseENSDF(filename);
655 } else {
656 std::cout << "Unknown extension " << ext << " => unknown data format" << std::endl;
657 }
658 }
659 }
660 }
661
662 fLevelSchemes.push_back(this);
663}
664
665TLevelScheme::TLevelScheme(const TLevelScheme& rhs)
666 : TPaveLabel(rhs), fDebug(rhs.fDebug), fBands(rhs.fBands), fAuxillaryLevels(rhs.fAuxillaryLevels),
667 fQValue(rhs.fQValue), fQValueUncertainty(rhs.fQValueUncertainty),
668 fNeutronSeparation(rhs.fNeutronSeparation), fNeutronSeparationUncertainty(rhs.fNeutronSeparationUncertainty),
669 fGammaWidth(rhs.fGammaWidth), fGammaDistance(rhs.fGammaDistance), fBandGap(rhs.fBandGap),
670 fLeftMargin(rhs.fLeftMargin), fRightMargin(rhs.fRightMargin), fBottomMargin(rhs.fBottomMargin), fTopMargin(rhs.fTopMargin),
671 fMinWidth(rhs.fMinWidth), fMaxWidth(rhs.fMaxWidth)
672{
673 fLevelSchemes.push_back(this);
674}
675
676void TLevelScheme::ListLevelSchemes()
677{
678 for(auto& scheme : fLevelSchemes) {
679 std::cout << " \"" << scheme->GetLabel() << "\"";
680 }
681 std::cout << std::endl;
682}
683
684TLevelScheme* TLevelScheme::GetLevelScheme(const char* name)
685{
686 for(auto& scheme : fLevelSchemes) {
687 if(strcmp(name, scheme->GetLabel()) == 0) {
688 return scheme;
689 }
690 }
691
692 std::cout << "Failed to find level scheme \"" << name << "\", " << fLevelSchemes.size() << " level schemes exist";
693 ListLevelSchemes();
694
695 return nullptr;
696}
697
698TLevel* TLevelScheme::AddLevel(const double energy, const std::string& bandName, const std::string& label)
699{
700 /// Add level at specified energy to specified band and give it the provided label.
701 /// Can be called from context menu using const char* instead of std::string.
702 if(fDebug) { Print(); }
703
704 for(auto& band : fBands) {
705 if(bandName == band.GetLabel()) {
706 if(fDebug) {
707 std::cout << "Found band \"" << band.GetLabel() << "\" (" << &band << ") with " << band.NofLevels() << " levels" << std::endl;
708 }
709 return band.AddLevel(energy, label);
710 }
711 if(fDebug) {
712 std::cout << "Band \"" << band.GetLabel() << "\" does not match " << bandName << std::endl;
713 }
714 }
715 fBands.emplace_back(this, bandName);
716 auto& newBand = fBands.back();
717 newBand.Debug(fDebug);
718 if(fDebug) {
719 std::cout << "Created band \"" << newBand.GetLabel() << "\" (" << &newBand << ") with " << newBand.NofLevels() << " level" << std::endl;
720 newBand.Print();
721 std::cout << std::endl;
722 }
723 return newBand.AddLevel(energy, label);
724}
725
726TLevel* TLevelScheme::GetLevel(double energy)
727{
728 for(auto& band : fBands) {
729 auto* level = band.GetLevel(energy);
730 if(level != nullptr) {
731 return level;
732 }
733 }
734 // if we reach here we failed to find a level with this energy
735 // output bands with min/max levels and return null pointer
736 std::cout << "Failed to find level with energy " << energy << " in " << fBands.size() << " bands:" << std::endl;
737 for(const auto& band : fBands) {
738 band.Print();
739 }
740
741 return nullptr;
742}
743
744TLevel* TLevelScheme::FindLevel(double energy, double energyUncertainty)
745{
746 for(auto& band : fBands) {
747 auto* level = band.FindLevel(energy, energyUncertainty);
748 if(level != nullptr) {
749 return level;
750 }
751 }
752 // if we reach here we failed to find a level with this energy
753 // output bands with min/max levels and return null pointer
754 std::cout << "Failed to find level with energy " << energy << " +- " << energyUncertainty << " in " << fBands.size() << " bands:" << std::endl;
755 for(const auto& band : fBands) {
756 band.Print();
757 }
758
759 return nullptr;
760}
761
762TGamma* TLevelScheme::FindGamma(double energy, double energyUncertainty)
763{
764 /// Finds gamma ray in range [energy - energyUncertainty, energy + energyUncertainty].
765 /// If multiple gamma rays are in this range it returns the one closest to the given energy.
766 auto list = FindGammas(energy - energyUncertainty, energy + energyUncertainty);
767
768 if(list.empty()) {
769 std::cerr << "Failed to find any gamma ray in range [" << energy - energyUncertainty << ", " << energy + energyUncertainty << "]" << std::endl;
770 return nullptr;
771 }
772
773 auto* result = list[0];
774 for(size_t i = 1; i < list.size(); ++i) {
775 if(std::fabs(list[i]->Energy() - energy) < std::fabs(result->Energy() - energy)) { result = list[i]; }
776 }
777
778 return result;
779}
780
781std::vector<TGamma*> TLevelScheme::FindGammas(double lowEnergy, double highEnergy)
782{
783 /// Returns a list of all gamma-rays in the range [lowEnergy, highEnergy]
784 std::vector<TGamma*> result;
785 // loop over all bands
786 for(auto& band : fBands) {
787 // loop over all levels in this band
788 for(auto& [levelEnergy, level] : band) {
789 // loop over all gamma-rays in the level
790 for(auto& [finalEnergy, gamma] : level) {
791 if(lowEnergy <= gamma.Energy() && gamma.Energy() <= highEnergy) {
792 result.push_back(&gamma);
793 }
794 }
795 }
796 }
797
798 return result;
799}
800
801void TLevelScheme::BuildGammaMap(double levelEnergy)
802{
803 /// Build a map of all gamma rays that populate levels equal and greater than levelEnergy.
804 /// This map is used to determine all gammas feeding a specific level.
805 /// Does nothing if the map isn't empty.
806 if(!fGammaMap.empty()) { return; }
807
808 for(auto& band : fBands) {
809 for(auto& [energy, level] : band) {
810 if(energy <= levelEnergy) {
811 // if the level is below this level or this level itself, gamma rays draining it can't feed this level
812 continue;
813 }
814 for(auto& [finalEnergy, gamma] : level) {
815 if(finalEnergy >= levelEnergy) {
816 fGammaMap.insert({finalEnergy, &gamma});
817 }
818 }
819 }
820 }
821 if(fDebug) {
822 std::cout << "Built map of " << fGammaMap.size() << " gamma rays populating levels above or equal " << levelEnergy << " keV" << std::endl;
823 }
824}
825
826std::map<double, double> TLevelScheme::FeedingGammas(double levelEnergy, double factor)
827{
828 /// Returns the energies and relative strengths of all gamma rays feeding the level at <energy>.
829 /// Gamma rays may appear multiple times with different strengths, if they are part of multiple cascades.
830 /// This search is a bit complicated, we need to find all gammas that feed this level.
831 /// Since this requires looping over all gamma rays, and we do this recursize, we will populate a single map with all gammas above this one.
832 /// That map has to be deleted before calling this function by calling TLevelScheme::ResetGammaMap!
833 std::map<double, double> result;
834 BuildGammaMap(levelEnergy);
835
836 auto range = fGammaMap.equal_range(levelEnergy);
837 if(fDebug) {
838 std::cout << "Got " << std::distance(range.first, range.second) << " gamma rays feeding level at " << levelEnergy << " kev (factor " << factor << ")" << std::endl;
839 }
840 for(auto& it = range.first; it != range.second; ++it) {
841 if(fDebug) {
842 std::cout << "Adding gamma " << it->second << ": ";
843 it->second->Print();
844 }
845 result[it->second->Energy()] += factor * it->second->BranchingRatioPercent();
846 auto tmp = FeedingGammas(it->second->InitialEnergy(), factor * it->second->BranchingRatioPercent());
847 for(auto& [energy, tmpFactor] : tmp) {
848 result[energy] += tmpFactor;
849 }
850 }
851
852 if(fDebug) {
853 std::cout << "returning list of " << result.size() << " gammas feeding level at " << levelEnergy << " keV" << std::endl;
854 }
855 return result;
856}
857
858std::map<double, double> TLevelScheme::DrainingGammas(double levelEnergy, double factor)
859{
860 /// Returns the energies and relative strengths of all gamma rays draining the level at <energy>.
861 /// Gamma rays may appear multiple times with different strengths, if they are part of multiple cascades.
862
863 // Since the branching ratio of a gamma ray cascades down to all gamma rays below it, we want to follow each
864 // cascade until we reach the ground state. So we get the level whos energy was provided and loop over it's gamma rays
865 // and for each gamma ray we get the next level and it's gamma rays
866 auto* level = GetLevel(levelEnergy);
867 std::map<double, double> result;
868 if(level == nullptr) {
869 std::cerr << "Failed to find level at " << levelEnergy << " keV, returning empty list of gammas draining that level" << std::endl;
870 Print();
871 return result;
872 }
873
874 if(fDebug) {
875 std::cout << level << ": looping over " << level->NofDrainingGammas() << " gammas for level at " << levelEnergy << " keV (factor " << factor << ")" << std::endl;
876 }
877
878 // loop over all gamma rays
879 for(auto& [finalEnergy, gamma] : *level) {
880 if(fDebug) {
881 std::cout << "Adding gamma: ";
882 gamma.Print();
883 }
884 // add this gamma to the result
885 auto branchingRatio = gamma.BranchingRatioPercent();
886 result[gamma.Energy()] += factor * branchingRatio;
887 // if we aren't at the ground state, add all gammas from the new level as well
888 if(gamma.FinalEnergy() > 0.) {
889 auto tmp = DrainingGammas(finalEnergy, factor * branchingRatio);
890 for(auto& [energy, tmpFactor] : tmp) {
891 result[energy] += tmpFactor;
892 }
893 }
894 }
895
896 if(fDebug) {
897 std::cout << "returning list of " << result.size() << " gammas draining level at " << levelEnergy << " keV" << std::endl;
898 }
899 return result;
900}
901
902std::vector<std::tuple<double, std::vector<double>>> TLevelScheme::ParallelGammas(double initialEnergy, double finalEnergy, double factor)
903{
904 /// This (recursive) function returns the combined probability and energies of gamma rays that are connecting the levels at initial and final energy.
905
906 auto* level = GetLevel(initialEnergy);
907 std::vector<std::tuple<double, std::vector<double>>> result;
908 if(level == nullptr) {
909 std::cerr << "Failed to find level at " << initialEnergy << " keV, returning empty list of gammas" << std::endl;
910 Print();
911 return result;
912 }
913
914 if(fDebug) {
915 std::cout << level << ": looping over " << level->NofDrainingGammas() << " gammas for level at " << initialEnergy << " keV (factor " << factor << ")" << std::endl;
916 }
917
918 result.emplace_back(1., std::vector<double>());
919 // loop over all gamma rays
920 for(auto& [levelEnergy, gamma] : *level) {
921 // if the level we populate with this gamma is below the final energy, we skip it
922 if(levelEnergy < finalEnergy) {
923 if(fDebug) {
924 std::cout << "skipping gamma ";
925 gamma.Print();
926 }
927 continue;
928 }
929 // add this gamma ray to the path for now
930 std::get<0>(result.back()) *= gamma.BranchingRatioPercent();
931 std::get<1>(result.back()).insert(std::get<1>(result.back()).end(), gamma.Energy());
932 if(fDebug) {
933 std::cout << "Added gamma ";
934 gamma.Print();
935 std::cout << "New result:";
936 for(auto& entry : result) {
937 std::cout << " " << std::get<0>(entry) << "-";
938 for(auto& e : std::get<1>(entry)) {
939 std::cout << e << ",";
940 }
941 }
942 std::cout << std::endl;
943 }
944 // if the level we populate is above the final energy, we see if we can find a gamma from that energy to the final energy
945 if(levelEnergy > finalEnergy) {
946 auto tmp = ParallelGammas(levelEnergy, finalEnergy, factor * gamma.BranchingRatioPercent());
947 // if we have multiple combinations, we don't just want to add all of them to one single entry, but create separate entries for each
948 // so we want to add n-1 copies of the last entry, this does not work if n is zero, so we check for that first
949 if(!tmp.empty()) {
950 auto it = result.insert(result.end(), tmp.size() - 1, result.back());
951 // insert returns position of first inserted element or pos (=.end() in this case) if count is zero, so we need to decrement by one
952 --it;
953 if(fDebug) {
954 std::cout << "Got " << tmp.size() << " paths, so added " << tmp.size() - 1 << " copies of last element:";
955 for(auto& entry : result) {
956 std::cout << " " << std::get<0>(entry) << "-";
957 for(auto& e : std::get<1>(entry)) {
958 std::cout << e << ",";
959 }
960 }
961 std::cout << std::endl;
962 }
963 for(auto& combo : tmp) {
964 // maybe we can reject any that don't reach the final energy already here???
965 std::get<0>(*it) *= std::get<0>(combo);
966 std::get<1>(*it).insert(std::get<1>(*it).end(), std::get<1>(combo).begin(), std::get<1>(combo).end());
967 ++it;
968 }
969 if(fDebug) {
970 std::cout << "Got " << tmp.size() << " more gammas, new result:";
971 for(auto& entry : result) {
972 std::cout << " " << std::get<0>(entry) << "-";
973 double sum = 0.;
974 for(auto& e : std::get<1>(entry)) {
975 std::cout << e << ",";
976 sum += e;
977 }
978 std::cout << "total " << sum;
979 }
980 std::cout << std::endl;
981 }
982 } else {
983 // we failed to find a path from the level this gamma populates to the final level, so we need to remove it from the path
984 // that is equivalent to re-setting the factor to one and clearing the vector of gammas
985 // std::get<0>(result.back()) = 1.;
986 // std::get<1>(result.back()).clear();
987 result.pop_back();
988 if(fDebug) {
989 std::cout << "Failed to find path from " << levelEnergy << " to " << finalEnergy << ", removed last gamma ray";
990 for(auto& entry : result) {
991 std::cout << " " << std::get<0>(entry) << "-";
992 for(auto& e : std::get<1>(entry)) {
993 std::cout << e << ",";
994 }
995 }
996 std::cout << std::endl;
997 }
998 }
999 }
1000 // if we found a path to the final energy (either via more gamma rays or by this one), we add this one and
1001 // since we are done with this path, we can add a new one (otherwise we will add to this path)
1002 result.emplace_back(1., std::vector<double>());
1003 if(fDebug) {
1004 std::cout << "Reached final level, added new (empty) entry to result" << std::endl;
1005 }
1006 } // loop over all gammas
1007
1008 // delete last entry if it's empty
1009 if(std::get<1>(result.back()).empty()) {
1010 result.pop_back();
1011 }
1012
1013 return result;
1014}
1015
1016void TLevelScheme::MoveToBand(const char* bandName, TLevel* level)
1017{
1018 /// Moves provided level to (new) band with provided name.
1019 // Try and find an existing band with this name to add the level to.
1020 if(fDebug) {
1021 std::cout << "TLevelScheme: Trying to move level " << level << " at " << level->Energy() << " keV to band \"" << bandName << "\"" << std::endl;
1022 }
1023 size_t i = 0;
1024 for(i = 0; i < fBands.size(); ++i) {
1025 if(strcmp(bandName, fBands[i].GetLabel()) == 0) {
1026 if(fDebug) {
1027 std::cout << "Found band \"" << fBands[i].GetLabel() << "\" (" << &fBands[i] << ") with " << fBands[i].NofLevels() << " levels" << std::endl;
1028 }
1029 fBands[i].AddLevel(level);
1030 break;
1031 }
1032 if(fDebug) {
1033 std::cout << "Band \"" << fBands[i].GetLabel() << "\" does not match " << bandName << std::endl;
1034 }
1035 }
1036 // if we didn't find a band to add this level to, create a new one and add the level
1037 if(i == fBands.size()) {
1038 if(fDebug) {
1039 std::cout << "Haven't found band \"" << bandName << "\" among existing bands, creating new band" << std::endl;
1040 }
1041 fBands.emplace_back(this, bandName);
1042 auto& newBand = fBands.back();
1043 newBand.Debug(fDebug);
1044 if(fDebug) {
1045 std::cout << "Created band \"" << newBand.GetLabel() << "\" (" << &newBand << ") with " << newBand.NofLevels() << " level" << std::endl;
1046 newBand.Print();
1047 std::cout << std::endl;
1048 }
1049 newBand.AddLevel(level);
1050 }
1051 // we added the level, so now we can loop through all bands whose name doesn't match the provided name and delete the level from them.
1052 for(auto& band : fBands) {
1053 if(fDebug) {
1054 std::cout << "Checking band \"" << band.GetLabel() << "\": ";
1055 }
1056 if(strcmp(bandName, band.GetLabel()) == 0) {
1057 if(fDebug) {
1058 std::cout << "this is the new band, not deleting it here!" << std::endl;
1059 }
1060 continue;
1061 }
1062 if(fDebug) {
1063 std::cout << "this is not the new band, removing it here!" << std::endl;
1064 }
1065 band.RemoveLevel(level);
1066 }
1067
1068 Refresh();
1069}
1070
1071void TLevelScheme::Refresh()
1072{
1073 // only re-draw if we find a matching canvas
1074 auto* canvas = static_cast<GCanvas*>(gROOT->GetListOfCanvases()->FindObject("LevelScheme"));
1075 if(canvas != nullptr) {
1076 Draw();
1077 }
1078}
1079
1080void TLevelScheme::UnZoom() const
1081{
1082 auto* canvas = static_cast<GCanvas*>(gROOT->GetListOfCanvases()->FindObject("LevelScheme"));
1083 if(canvas != nullptr) {
1084 canvas->Range(fX1, fY1, fX2, fY2);
1085 canvas->Modified();
1086 canvas->Update();
1087 }
1088}
1089
1090void TLevelScheme::Draw(Option_t*)
1091{
1092 if(fDebug) { std::cout << __PRETTY_FUNCTION__ << std::endl; } // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
1093 auto* canvas = static_cast<GCanvas*>(gROOT->GetListOfCanvases()->FindObject("LevelScheme"));
1094 if(canvas == nullptr) {
1095 canvas = new GCanvas("LevelScheme", "Level Scheme");
1096 } else {
1097 canvas->Clear();
1098 }
1099
1100 // find the lowest and highest level by going through each band and getting the lowest and highest levels
1101 // also get the width by adding up all level widths and get the strongest and weakest gamma ray of each band
1102 auto minMaxLevel = fBands[0].GetMinMaxLevelEnergy();
1103 double width = 0.;
1104 std::vector<std::pair<double, double>> minMaxGamma;
1105 for(auto& band : fBands) {
1106 auto minMax = band.GetMinMaxLevelEnergy();
1107 if(minMax.first < minMaxLevel.first) { minMaxLevel.first = minMax.first; }
1108 if(minMax.second > minMaxLevel.second) { minMaxLevel.second = minMax.second; }
1109 if(fDebug) { std::cout << "Incrementing width from " << width; }
1110 width += band.Width(fGammaDistance);
1111 if(fDebug) { std::cout << " to " << width << " using " << band.Width(fGammaDistance) << std::endl; }
1112 minMaxGamma.push_back(band.GetMinMaxGamma());
1113 }
1114 width += static_cast<double>(fBands.size() - 1) * fBandGap;
1115
1116 // ys calculated from the lowest and highest level plus the bottom and top margins
1117 fY1 = minMaxLevel.first;
1118 fY2 = minMaxLevel.second;
1119 double height = fY2 - fY1;
1120 // if the margins haven't been set, we add 10% of the height
1121 if(fBottomMargin < 0) {
1122 fY1 -= height / 10.;
1123 } else {
1124 fY1 -= fBottomMargin;
1125 }
1126 if(fTopMargin < 0) {
1127 fY2 += height / 10.;
1128 } else {
1129 fY2 += fTopMargin;
1130 }
1131
1132 // xs are calculated from the width of each band, plus the left and right margin, plus n-1 times the margin between bands
1133 fX1 = 0.;
1134 fX2 = width;
1135 // if the margins haven't been set, we add the band gap
1136 if(fLeftMargin < 0) {
1137 fX1 -= fBandGap;
1138 } else {
1139 fX1 -= fLeftMargin;
1140 }
1141 if(fRightMargin < 0) {
1142 fX2 += fBandGap;
1143 } else {
1144 fX2 += fRightMargin;
1145 }
1146
1147 if(fDebug) { std::cout << "got x1 - x2 " << fX1 << " - " << fX2 << ", and y1 - y2 " << fY1 << " - " << fY2 << std::endl; }
1148 canvas->Range(fX1, fY1, fX2, fY2);
1149 canvas->cd();
1150
1151 SetX1(fX2 - fBandGap);
1152 SetX2(fX2);
1153 if(fTopMargin < 0) {
1154 SetY1(fX2 - height / 12.);
1155 } else {
1156 SetY1(fX2 - fTopMargin * 0.75);
1157 }
1158 SetY2(fX2);
1159 SetTextSize(0); // default size (?)
1160 SetTextAlign(22); // centered in x and y
1161 TPaveLabel::Draw();
1162
1163 if(fGammaWidth == EGammaWidth::kGlobal) {
1164 // if we want the gamma width to be on a global scale, i.e. across all bands, we need to find the minimum and maximum values
1165 // we store those in the first entry
1166 for(auto& [min, max] : minMaxGamma) {
1167 if(min < minMaxGamma[0].first) { minMaxGamma[0].first = min; }
1168 if(max > minMaxGamma[0].second) { minMaxGamma[0].second = max; }
1169 std::cout << min << " - " << max << ": " << minMaxGamma[0].first << " - " << minMaxGamma[0].second << std::endl;
1170 }
1171 }
1172
1173 // loop over all levels in all bands and draw them
1174 // for each band we need to determine left and right position, we start at 0
1175 double left = 0.;
1176 for(size_t b = 0; b < fBands.size(); ++b) {
1177 double right = left + fBands[b].Width(fGammaDistance);
1178 if(fDebug) { std::cout << b << ": Using width " << fBands[b].Width(fGammaDistance) << " and left " << left << " we get right " << right << std::endl; }
1179 fBands[b].SetX1(left);
1180 fBands[b].SetY1(-1.5 * height / 20.);
1181 fBands[b].SetX2(right);
1182 fBands[b].SetY2(-height / 40.);
1183 fBands[b].SetFillColor(10);
1184 fBands[b].Draw();
1185 // get the scaling for the gamma's so that the widths are between fMinWidth and fMaxWidth
1186 // for global scaling we use the first minMax where we stored the global mininum and maximum
1187 auto& [min, max] = minMaxGamma[0];
1188 if(fGammaWidth == EGammaWidth::kBand) {
1189 // if we scale per band, we set the minimum and maximum for each band
1190 min = minMaxGamma[b].first;
1191 max = minMaxGamma[b].second;
1192 }
1193 double scalingGain = (fMaxWidth - fMinWidth) / (max - min);
1194 double scalingOffset = fMinWidth - scalingGain * min;
1195 if(fGammaWidth == EGammaWidth::kNoWidth) {
1196 // if we don't use the width at all, we set the scaling by hand so the width is always the minimum width
1197 scalingGain = 0.;
1198 scalingOffset = fMinWidth;
1199 }
1200
1201 double labelSize = TLevel::TextSize() * std::min(fX2 - fX1, fY2 - fY1);
1202 labelSize *= 1.5; // we want a bit of a gap between labels ...
1203
1204 // loop to calculate "center" for closely grouped levels
1205 // to calculate the necessary offset we need to know for each group the center and how many levels are there between this one and the center
1206 // first let's find the groups of levels, need to first check all groups the current level *could* belong to,
1207 // as #1 and #2 might not overlap (so be in two different groups) but then #3 overlaps with both #1 and #2 (and would end in the group of #1 if we don't check this)
1208 // so we create a list of groups this level can belong to, then if it's 0 we create a new group, if it's 1 we add it to it, if it's more than 1 we combine the groups
1209 std::vector<std::vector<std::pair<double, int>>> groups;
1210 int index = 0;
1211 for(auto& [energy, level] : fBands[b]) {
1212 std::vector<size_t> potentialGroups;
1213 for(size_t i = 0; i < groups.size(); ++i) {
1214 for(size_t j = 0; j < groups[i].size(); ++j) {
1215 // don't compare to the levels of the group as is, but assume that they are going to be shifted by average - energy + label size * (index - (# in group - 1)/2)
1216 // so the energy we want to compare to is average + label size * (index - (# in group - 1)/2)
1217 // for that we need the average of the group at this point
1218 double average = std::accumulate(groups[i].begin(), groups[i].end(), 0., [](double r, std::pair<double, int> p) { r += p.first; return r; }) / static_cast<double>(groups[i].size());
1219 // we don't need abs here as we know the current energy and index are larger than the previous ones
1220 if(energy - (average + labelSize * (static_cast<double>(j) - static_cast<double>(groups[i].size() - 1) / 2.)) < labelSize * (index - groups[i][j].second)) {
1221 if(fDebug) {
1222 std::cout << energy - groups[i][j].first << " (" << energy << " - " << groups[i][j].first << ") < " << labelSize * (index - groups[i][j].second) << " (" << labelSize << "*(" << index << "-" << groups[i][j].second << ")), adding to group " << i << std::endl;
1223 }
1224 potentialGroups.push_back(i);
1225 // break here since we already know this group is a match
1226 break;
1227 }
1228 if(fDebug) {
1229 std::cout << energy - groups[i][j].first << " (" << energy << " - " << groups[i][j].first << ") >= " << labelSize * (index - groups[i][j].second) << " (" << labelSize << "*(" << index << "-" << groups[i][j].second << ")), NOT adding to group " << i << std::endl;
1230 }
1231 }
1232 // we don't want to break again here since we want to check all groups
1233 }
1234 if(potentialGroups.empty()) {
1235 groups.emplace_back(1, std::make_pair(energy, index));
1236 } else if(potentialGroups.size() == 1) {
1237 groups[potentialGroups[0]].push_back(std::make_pair(energy, index));
1238 } else {
1239 if(fDebug) {
1240 std::cout << "combining groups";
1241 for(const auto& potentialGroup : potentialGroups) {
1242 std::cout << " " << potentialGroup;
1243 }
1244 std::cout << std::endl;
1245 }
1246 // multiple potential groups, so we add all of them together, add this level, sort the resulting group and then remove the other groups
1247 for(auto potentialGroup : potentialGroups) {
1248 groups[potentialGroups[0]].insert(groups[potentialGroups[0]].end(), groups[potentialGroup].begin(), groups[potentialGroup].end());
1249 groups.erase(groups.begin() + potentialGroup);
1250 }
1251 groups[potentialGroups[0]].emplace_back(energy, index);
1252 std::sort(groups[potentialGroups[0]].begin(), groups[potentialGroups[0]].end());
1253 }
1254 ++index;
1255 }
1256 if(fDebug) {
1257 std::cout << "got " << groups.size() << " groups:" << std::endl;
1258 for(auto& group : groups) {
1259 for(auto& level : group) {
1260 std::cout << level.second << " " << level.first << ", ";
1261 }
1262 std::cout << std::endl;
1263 }
1264 }
1265 // we now have a vector of groups of levels, find the center of each group and how many levels are between it and the current level
1266 std::vector<std::pair<double, double>> centers;
1267 for(auto& group : groups) {
1268 double average = std::accumulate(group.begin(), group.end(), 0., [](double r, std::pair<double, double> p) { r += p.first; return r; }) / static_cast<double>(group.size());
1269 for(size_t i = 0; i < group.size(); ++i) {
1270 centers.emplace_back(average, static_cast<double>(i) - static_cast<double>(group.size() - 1) / 2.);
1271 }
1272 }
1273 if(fDebug) {
1274 std::cout << "centers: ";
1275 for(auto& [center, diff] : centers) {
1276 std::cout << center << " " << diff << ", ";
1277 }
1278 std::cout << std::endl;
1279 }
1280
1281 size_t g = 1; // counter for number of gammas in this band
1282 // loop over all levels of this band
1283 index = 0;
1284 for(auto& [energy, level] : fBands[b]) {
1285 if(fDebug) {
1286 std::cout << std::endl;
1287 std::cout << "starting calculations for drawing level at " << energy << std::endl;
1288 }
1289 // check level distance compare to size of labels
1290 double move = centers[index].first - energy + labelSize * centers[index].second;
1291 if(fDebug) {
1292 std::cout << "calculations for drawing level at " << energy << ": move " << move << " (" << centers[index].first << " - " << energy << " + " << labelSize << "*" << centers[index].second << ") => " << energy + move << ", " << left << "-" << right << std::endl;
1293 }
1294 level.Offset(move);
1295 level.Draw(left, right);
1296 // double labelWidth = level.DrawLabel(right);
1297 // double energyWidth = level.DrawEnergy(left);
1298 level.DrawLabel(right);
1299 level.DrawEnergy(left);
1300 // TODO: check these widths to see if we need to adjust the margins.
1301 // Should also adjust gaps between bands to be equal to their sum, but how?
1302 // If we call Draw recursively if we changed anything it will never stop (as that re-adjusts the text sizes).
1303 // Maybe only set a flag to re-draw when the change is larger than a minimum value?
1304 if(fDebug) { level.Print(); }
1305
1306 // loop over all gammas from this level and draw them
1307 for(auto& [finalEnergy, gamma] : level) {
1308 // find the final level, get it's energy and x-position
1309 size_t b2 = 0;
1310 bool found = false;
1311 for(b2 = 0; b2 < fBands.size(); ++b2) {
1312 for(auto& [energy2, level2] : fBands[b2]) {
1313 if(finalEnergy == level2.Energy()) {
1314 if(fDebug) {
1315 std::cout << "band " << b2 << ": " << finalEnergy << " == " << level2.Energy() << std::endl;
1316 }
1317 found = true;
1318 break;
1319 }
1320 if(fDebug) {
1321 std::cout << "band " << b2 << ": " << finalEnergy << " != " << level2.Energy() << std::endl;
1322 }
1323 }
1324 if(found) { break; }
1325 }
1326
1327 if(!found) {
1328 std::cout << "Warning, failed to find final level at " << finalEnergy << " keV for gamma-ray!" << std::endl;
1329 }
1330
1331 // set the scaling for the width
1332 gamma.Scaling(scalingOffset, scalingGain);
1333
1334 // y-position is easy, simply grab the energy of the initial and final level
1335 double gY1 = level.Energy();
1336 double gY2 = finalEnergy;
1337
1338 // x-position is more complicated, depends whether it's intra- or inter-band
1339 // and what other gamma rays are there at this energy range
1340 double gX1 = 0.;
1341 double gX2 = 0.;
1342 if(fRadwareStyle) {
1343 if(b == b2) { // intra-band: for now just increment the position, later maybe search all previously added intra-band transitions for this band
1344 if(fDebug) { std::cout << b << " == " << b2 << ": intra band " << g << " at position " << static_cast<double>(g) * fGammaDistance << std::endl; }
1345 gX1 = left + static_cast<double>(g) * fGammaDistance;
1346 gX2 = left + static_cast<double>(g) * fGammaDistance;
1347 ++g;
1348 } else if(b < b2) { // inter-band to a band on the right
1349 if(fDebug) { std::cout << b << " < " << b2 << ": inter band to right " << g << " at position " << static_cast<double>(g) * fGammaDistance << std::endl; }
1350 gX1 = left + static_cast<double>(g) * fGammaDistance;
1351 gX2 = left + static_cast<double>(g) * fGammaDistance + (gY1 - gY2) / 10.;
1352 double shift = right + fBandGap;
1353 // sum the width of bands b+1 to b2-1 and the band gaps between them
1354 if(b + 1 < b2) {
1355 shift = std::accumulate(fBands.begin() + b + 1, fBands.begin() + b2, right + fBandGap, [&](double r, TBand& el) { r += el.Width(fGammaDistance) + fBandGap; return r; });
1356 }
1357 DrawAuxillaryLevel(gY2, right + static_cast<double>(g) * fGammaDistance, shift - fBandGap / 2.); // for now always a gap of fBandGap/2. for the label
1358 ++g;
1359 } else { // inter-band to a band on the left
1360 if(fDebug) { std::cout << b << " > " << b2 << ": inter band to left " << g << " at position " << static_cast<double>(g) * fGammaDistance << std::endl; }
1361 gX1 = left + static_cast<double>(g) * fGammaDistance;
1362 gX2 = left + static_cast<double>(g) * fGammaDistance - (gY1 - gY2) / 10.;
1363 double shift = left - fBandGap;
1364 // sum the width of bands b2+1 to b-1 and the band gaps between them
1365 if(b2 + 1 < b) {
1366 shift = std::accumulate(fBands.begin() + b2 + 1, fBands.begin() + b, left - fBandGap, [&](double r, TBand& el) { r -= el.Width(fGammaDistance) + fBandGap; return r; });
1367 }
1368 DrawAuxillaryLevel(gY2, left + static_cast<double>(g) * fGammaDistance, shift + fBandGap / 2.);
1369 ++g;
1370 }
1371 } else {
1372 if(b == b2) { // intra-band: for now just increment the position, later maybe search all previously added intra-band transitions for this band
1373 if(fDebug) { std::cout << b << " == " << b2 << ": intra band " << g << " at position " << static_cast<double>(g) * fGammaDistance << std::endl; }
1374 gX1 = left + static_cast<double>(g) * fGammaDistance;
1375 gX2 = left + static_cast<double>(g) * fGammaDistance;
1376 ++g;
1377 } else if(b + 1 == b2) { // inter-band from this band to the next band on the right
1378 if(fDebug) { std::cout << b << "+1 == " << b2 << ": inter band " << g << " to right " << right << "-" << right + fBandGap << std::endl; }
1379 gX1 = right - fGammaDistance / 2.;
1380 gX2 = right + fBandGap + fGammaDistance / 2.;
1381 } else if(b == b2 + 1) { // inter-band from this band to the next band on the left
1382 if(fDebug) { std::cout << b << " == " << b2 << "+1: inter band " << g << " to left " << left << "-" << left - fBandGap << std::endl; }
1383 gX1 = left + fGammaDistance / 2.;
1384 gX2 = left - fBandGap - fGammaDistance / 2.;
1385 } else if(b < b2) { // inter-band to a band on the right that is not a direct neighbour
1386 if(fDebug) { std::cout << b << " < " << b2 << ": inter band far " << g << " right " << right << ", band gap " << fBandGap << std::endl; }
1387 gX1 = right;
1388 gX2 = right + fBandGap / 8.;
1389 double shift = right + fBandGap;
1390 // sum the width of bands b+1 to b2-1 and the band gaps between them
1391 if(b + 1 < b2) {
1392 shift = std::accumulate(fBands.begin() + b + 1, fBands.begin() + b2, right + fBandGap, [&](double r, TBand& el) { r += el.Width(fGammaDistance) + fBandGap; return r; });
1393 }
1394 DrawAuxillaryLevel(gY2, right, shift - fBandGap / 2.);
1395 } else { // inter-band to a band on the left that is not a direct neighbour
1396 if(fDebug) { std::cout << b << " > " << b2 << ": inter band far " << g << " left " << left << ", band gap " << fBandGap << std::endl; }
1397 gX1 = left;
1398 gX2 = left - fBandGap / 8.;
1399 double shift = left - fBandGap;
1400 // sum the width of bands b2+1 to b-1 and the band gaps between them
1401 if(b2 + 1 < b) {
1402 shift = std::accumulate(fBands.begin() + b2 + 1, fBands.begin() + b, left - fBandGap, [&](double r, TBand& el) { r -= el.Width(fGammaDistance) + fBandGap; return r; });
1403 }
1404 DrawAuxillaryLevel(gY2, left, shift + fBandGap / 2.);
1405 }
1406 }
1407
1408 gamma.Draw(gX1, gY1, gX2, gY2);
1409 } // end of gamma loop
1410 if(level.begin() != level.end()) {
1411 if(fDebug) {
1412 std::cout << "Level has gamma-rays, incrementing g from " << g << std::endl;
1413 level.Print();
1414 }
1415 ++g; // add an extra space at the end of each level that has gamma rays
1416 }
1417 ++index;
1418 }
1419 // update left before we go to the next band
1420 left = right + fBandGap;
1421 }
1422
1423 if(fDebug) {
1424 std::cout << "Canvas " << canvas << " " << canvas->GetName() << " has " << canvas->GetListOfPrimitives()->GetSize() << " primitives:" << std::endl;
1425 canvas->GetListOfPrimitives()->Print();
1426 Print();
1427 }
1428}
1429
1430void TLevelScheme::DrawAuxillaryLevel(const double& energy, const double& left, const double& right)
1431{
1432 if(fDebug) {
1433 std::cout << "Drawing auxillary level at " << energy << " keV from " << left << " to " << right << std::endl;
1434 }
1435 // maybe change this to be a short solid line connected by a dotted line to the original level?
1436 if(fAuxillaryLevels.count(energy) > 0) {
1437 // we have multiple auxillary levels => try and find one with matching left and right
1438 auto range = fAuxillaryLevels.equal_range(energy);
1439 for(auto it = range.first; it != range.second; ++it) {
1440 if(it->second.GetX1() == left && it->second.GetX2() == right) { return; }
1441 }
1442 }
1443 // haven't found a matching level, so we add a new one
1444 auto it = fAuxillaryLevels.emplace(std::piecewise_construct, std::forward_as_tuple(energy), std::forward_as_tuple(left, energy, right, energy));
1445 it->second.SetLineStyle(2);
1446 it->second.Draw();
1447}
1448
1449void TLevelScheme::Print(Option_t*) const
1450{
1451 std::cout << this << ": got " << fBands.size() << " bands: ";
1452 for(size_t b = 0; b < fBands.size(); ++b) {
1453 std::cout << " " << b << "-" << &fBands[b] << "-" << fBands[b].GetLabel();
1454 }
1455 std::cout << ", debugging" << (fDebug ? "" : " not") << " enabled" << std::endl;
1456 if(fDebug) {
1457 for(const auto& band : fBands) {
1458 band.Print();
1459 }
1460 }
1461}
1462
1463void TLevelScheme::ParseENSDF(const std::string& filename)
1464{
1465 /// This function parses the given file assuming it's ENSDF formatted.
1466 // TODO: check if file exists
1467 std::ifstream input(filename);
1468 if(!input.is_open()) {
1469 std::cerr << DRED << "Failed to open \"" << filename << "\"" << RESET_COLOR << std::endl;
1470 return;
1471 }
1472
1473 std::string line;
1474 std::istringstream str;
1475 TLevel* currentLevel = nullptr;
1476
1477 // general identifier format of line (first 8 characters)
1478 // 1-3 mass
1479 // 4-5 element symbol (except for reference record where it's blank)
1480 // 6 blank/'1' for primary, any ascii character (but not '1) for continuation
1481 // 7 blank or c, C, d, D, t, or T for comment
1482 // 8 R - reference, X - cross reference, H - history, Q - q-value, P - parent, N - normalization, L - level, B - beta, E - EC, A - alpha, D - delayed particle, G - gamma
1483
1484 // first line should be identification record (1-5 nuclide, 10-39 data set ident., 40-65 refs., 66-74 publ. inf., 75-80 date
1485 std::getline(input, line);
1486 SetLabel(line.substr(0, 5).c_str());
1487 // trimWS(fNuclide); // trim whitespace
1488 std::cout << "Reading level scheme for " << GetLabel() << std::endl;
1489 // check that data set ident is "ADOPTED LEVELS, GAMMAS" or at least "ADOPTED LEVELS"
1490 if(line.substr(9, 14) == "ADOPTED LEVELS") {
1491 std::cout << R"(Data set is not "ADOPTED LEVELS" or "ADOPTED LEVELS, GAMMAS", but ")" << line.substr(9, 14) << "\", don't know how to read that (" << line << ")" << std::endl;
1492 return;
1493 }
1494 // if the identification record is continued, 6 will not be blank
1495 do {
1496 std::getline(input, line);
1497 } while(line[5] != ' ');
1498
1499 // now we have the first line that is not part of the identification record, so we check it's format and continue reading lines as long as we can
1500 do {
1501 switch(line[7]) {
1502 case 'H': // history record (1-5 nuclide, 6 blank or anything but '1' for cont., 7 blank, 8 'H', 9 blank, 10-80 history)
1503 // ignored
1504 break;
1505 case 'Q': // q-value record (1-5 nuclide, 6-9 " Q ", 10-19 beta- q-value, 20-21 uncert., 22-29 S_n, 30-31 uncert., 32-39 S_p, 40-41 uncert., 42-49 alpha q-value, 50-51 uncert., 56-80 refs.)
1506 if(line.substr(5, 4) == " Q ") {
1507 // we only read the beta- q-value and the neutron separation energy
1508 str.clear();
1509 str.str(line.substr(9, 10));
1510 str >> fQValue;
1511 str.clear();
1512 str.str(line.substr(19, 2));
1513 str >> fQValueUncertainty;
1514 if(fDebug) { std::cout << "reading S_n old \"" << str.str() << "\" @ " << str.tellg() << (str.fail() ? " failed" : " good"); }
1515 str.clear();
1516 str.str(line.substr(21, 8)); // TODO: doesn't work?
1517 if(fDebug) { std::cout << ", new \"" << str.str() << "\" @ " << str.tellg() << (str.fail() ? " failed" : " good"); }
1518 str >> fNeutronSeparation;
1519 if(fDebug) { std::cout << " => S_n " << fNeutronSeparation << std::endl; }
1520 str.clear();
1521 str.str(line.substr(29, 2));
1522 str >> fNeutronSeparationUncertainty;
1523 if(fDebug) {
1524 std::cout << "Found q-value line: " << fQValue << " +- " << fQValueUncertainty << ", S_n " << fNeutronSeparation << " +- " << fNeutronSeparationUncertainty << std::endl;
1525 std::cout << "\"" << line.substr(9, 10) << "\", \"" << line.substr(19, 2) << "\", \"" << line.substr(21, 8) << "\", \"" << line.substr(29, 2) << "\"" << std::endl;
1526 }
1527 } else if(fDebug) { // if 7 is not blank this is a comment for the q-value and we ignore it
1528 std::cout << "Ignoring q-value comment \"" << line << "\"" << std::endl;
1529 }
1530 break;
1531 case 'X': // cross-reference record (1-5 nuclide, 6-7 blank, 8 'X', 9 identifier, 10-39 DSID used, 40-80 blank)
1532 // ignored
1533 if(fDebug) { std::cout << "Ignoring cross-reference \"" << line << "\"" << std::endl; }
1534 break;
1535 case 'P': // parent record (1-5 nuclide, 6-7 blank, 8 'P', 9 blank or integer, ...), seems to be for decay data sets only?
1536 if(fDebug) { std::cout << "Ignoring parent \"" << line << "\"" << std::endl; }
1537 break;
1538 case 'N': // this can be one of two records, depending on whether 7 is blank or 'P'
1539 // normalization record (1-5 nuclide, 6-7 blank, 8 'N', 9 blank or integer, ...), seems to be for decay data sets only?
1540 // production normalization record (1-5 nuclide, 6-9 " PN ", ...) ignored
1541 if(fDebug) { std::cout << "Ignoring normalization \"" << line << "\"" << std::endl; }
1542 break;
1543 case 'L': // level record (1-5 nuclide, 6 blank or not '1' for cont., 7-9 " L ", 10-19 energy keV, 20-21 uncert., 22-39 spin and parity, 40-49 half life with units, 50-55 uncert.
1544 // 56-74 angular momentum transfer in reaction, 75-76 uncert., 77 comment flag, 78-79 metastable as "M ", or "M1", "M2", etc., 80 '?' denotes uncertain level and 'S' denotes neutron, proton, alpha sep. en.)
1545 if(line[5] == ' ' && line[6] == ' ') {
1546 // read energy and energy uncertainty
1547 double energy = 0.;
1548 double energyUncertainty = 0.;
1549 if(fDebug) { std::cout << "reading level energy old \"" << str.str() << "\""; }
1550 str.clear();
1551 str.str(line.substr(9, 10));
1552 if(fDebug) { std::cout << ", new \"" << str.str() << "\""; }
1553 str >> energy;
1554 if(fDebug) { std::cout << " => energy " << energy << std::endl; }
1555 str.clear();
1556 str.str(line.substr(19, 2));
1557 str >> energyUncertainty;
1558 // read string for spin and parity, half-life, and half-life uncertainty seperately
1559 // reading a string from stringstream means we discard all leading whitespace and stop the moment we encounter more whitespace
1560 // i.e. we read only one word
1561 std::string spinParity = line.substr(21, 18);
1562 trimWS(spinParity);
1563 std::string halfLife = line.substr(39, 10); // include the unit!
1564 trimWS(halfLife);
1565 std::string halfLifeUncertainty = line.substr(49, 6);
1566 trimWS(halfLifeUncertainty);
1567 // combine all non-zero strings to get the label
1568 std::string label;
1569 if(!spinParity.empty()) {
1570 label += spinParity;
1571 }
1572 if(!halfLife.empty()) {
1573 label += halfLife;
1574 label += halfLifeUncertainty;
1575 }
1576 if(fDebug) {
1577 std::cout << "Adding new level " << energy << " +- " << energyUncertainty << ", " << spinParity << ", half-life " << halfLife << " +- " << halfLifeUncertainty << std::endl;
1578 std::cout << "\"" << line.substr(9, 10) << "\", \"" << line.substr(19, 2) << "\", \"" << line.substr(21, 18) << "\", \"" << line.substr(39, 10) << "\", \"" << line.substr(49, 6) << "\"" << std::endl;
1579 }
1580 // create new level and set it's energy uncertainty
1581 currentLevel = AddLevel(energy, GetLabel(), label);
1582 currentLevel->EnergyUncertainty(energyUncertainty);
1583 } else if(fDebug) {
1584 std::cout << "Ignoring level \"" << line << "\"" << std::endl;
1585 }
1586 break;
1587 case 'G': // gamma record (1-5 nuclide, 6 blank or not '1' for cont., 7-9 " G ", 10-19 energy in keV, 20-21 uncert. 22-29 relative photon intens., 30-31 uncert., 32-41 multipol.
1588 // 42-49 mixing ratio, 50-55 uncert., 56-62 conversion coeff., 63-64 uncert., 65-74 relative total intens., 75-76 uncert., 77 comment flag, 78 'C' confirmed coincidence, '?' questionable coincidence,
1589 // 79 blank, 80 '?' questionable placement, 'S' expected but unobserved)
1590 if(line[5] == ' ' && line[6] == ' ') {
1591 if(currentLevel == nullptr) {
1592 std::cout << "Found unassigned gamma, ignoring it (" << line << ")!" << std::endl;
1593 break;
1594 }
1595 // read energy and uncertainty
1596 double energy = 0.;
1597 double energyUncertainty = 0.;
1598 str.clear();
1599 str.str(line.substr(9, 10));
1600 str >> energy;
1601 str.clear();
1602 str.str(line.substr(19, 2));
1603 str >> energyUncertainty;
1604 // read relative photon intensity and uncertainty
1605 double photonIntensity = 0.;
1606 double photonIntensityUncertainty = 0.;
1607 str.clear();
1608 str.str(line.substr(21, 8));
1609 str >> photonIntensity;
1610 str.clear();
1611 str.str(line.substr(29, 2));
1612 str >> photonIntensityUncertainty;
1613 // read multipolarity
1614 std::string multipolarity = line.substr(31, 10);
1615 trimWS(multipolarity);
1616 // read mixing ratio and uncertainty
1617 double mixingRatio = 0.;
1618 double mixingRatioUncertainty = 0.;
1619 str.clear();
1620 str.str(line.substr(41, 8));
1621 str >> mixingRatio;
1622 str.clear();
1623 str.str(line.substr(49, 6));
1624 str >> mixingRatioUncertainty;
1625 // read conversion coeff. and uncertainty
1626 double conversionCoeff = 0.;
1627 double conversionCoeffUncertainty = 0.;
1628 str.clear();
1629 str.str(line.substr(55, 7));
1630 str >> conversionCoeff;
1631 str.clear();
1632 str.str(line.substr(62, 2));
1633 str >> conversionCoeffUncertainty;
1634 // read relative total intensity and uncertainty
1635 double totalIntensity = 0.;
1636 double totalIntensityUncertainty = 0.;
1637 str.clear();
1638 str.str(line.substr(64, 10));
1639 str >> totalIntensity;
1640 str.clear();
1641 str.str(line.substr(74, 2));
1642 str >> totalIntensityUncertainty;
1643 // we already checked that the current level is not a null pointer so we can add the gamma here
1644 if(fDebug) {
1645 std::cout << "Adding gamma with energy " << energy << " +- " << energyUncertainty << ", " << photonIntensity << " +- " << photonIntensityUncertainty << ", " << multipolarity << ", " << mixingRatio << " +- " << mixingRatioUncertainty << ", " << conversionCoeff << " +- " << conversionCoeffUncertainty << ", " << totalIntensity << " +- " << totalIntensityUncertainty << ", final level energy " << currentLevel->Energy() - energy << std::endl;
1646 std::cout << "\"" << line.substr(9, 10) << "\", \"" << line.substr(19, 2) << "\", \"" << line.substr(21, 8) << "\", \"" << line.substr(29, 2) << "\", \"" << line.substr(31, 10) << "\", \"" << line.substr(41, 8) << "\", \"" << line.substr(49, 6) << "\", \"" << line.substr(55, 7) << "\", \"" << line.substr(62, 2) << "\", \"" << line.substr(64, 10) << "\", \"" << line.substr(74, 2) << "\"" << std::endl;
1647 }
1648 auto* gamma = currentLevel->AddGamma(currentLevel->Energy() - energy, energyUncertainty, multipolarity.c_str(), photonIntensity, totalIntensity);
1649 if(gamma != nullptr) {
1650 gamma->BranchingRatioUncertainty(photonIntensityUncertainty);
1651 gamma->TransitionStrengthUncertainty(totalIntensityUncertainty);
1652 // currently ignoring mixing ratio and conversion coefficents
1653 }
1654 } else if(fDebug) {
1655 std::cout << "Ignoring gamma \"" << line << "\"" << std::endl;
1656 }
1657 break;
1658 case 'B': // beta record (1-5 nuclide, 6 blank or not '1' for cont., 7-9 " B ", ...), seems to be for decay data sets only?
1659 if(fDebug) { std::cout << "Ignoring beta \"" << line << "\"" << std::endl; }
1660 break;
1661 case 'E': // EC record (1-5 nuclide, 6 blank or not '1' for cont., 7-9 " E ", ...), seems to be for decay data sets only?
1662 if(fDebug) { std::cout << "Ignoring EC \"" << line << "\"" << std::endl; }
1663 break;
1664 case 'A': // alpha record (1-5 nuclide, 6 blank or not '1' for cont., 7-9 " A ", ...), seems to be for decay data sets only?
1665 if(fDebug) { std::cout << "Ignoring alpha \"" << line << "\"" << std::endl; }
1666 break;
1667 case 'D': // delayed particle record (1-5 nuclide, 6 blank or not '1' for cont., 7-8 " D", 9 particle N, P, or A, ...), seems to be for decay data sets only?
1668 if(fDebug) { std::cout << "Ignoring delayed \"" << line << "\"" << std::endl; }
1669 break;
1670 case 'R': // reference record (1-3 mass, 4-7 blank, 8 'R', 9 blank, ...) not present?
1671 if(fDebug) { std::cout << "Ignoring reference \"" << line << "\"" << std::endl; }
1672 break;
1673 case ' ': // comment (if line[6] is 'c' or maybe 'C', 'd', 'D', 't', or 'T')
1674 if(fDebug) { std::cout << "Ignoring comment \"" << line << "\"" << std::endl; }
1675 break;
1676 default:
1677 // probably a comment record (1-5 nuclide, 6 blank or character not '1' for cont., 7 'c', 'D', 'T', or 't', plus other stuff we ignore)
1678 if(fDebug) { std::cout << "Skipping unknown character " << line[7] << " from line \"" << line << "\"" << std::endl; }
1679 break;
1680 };
1681 } while(std::getline(input, line) && !line.empty());
1682
1683 if(fDebug) { std::cout << "Done reading \"" << filename << "\"" << std::endl; }
1684
1685 input.close();
1686}
1687#endif
#define DRED
Definition Globals.h:18
#define RESET_COLOR
Definition Globals.h:5
void trimWS(std::string &line)
bool FileExists(const char *filename)