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