MUSIC unknown
Analysis for the MUSIC active-target ionization chamber
Loading...
Searching...
No Matches
CrossSection.cpp
Go to the documentation of this file.
1#include "CrossSection.hpp"
2#include "BeamEnergies.hpp"
3#include "Constants.hpp"
4#include "InitUtils.hpp"
5#include "Paths.hpp"
6#include "PlottingUtils.hpp"
7#include "RegionCuts.hpp"
8#include "StripSumScatter.hpp"
9#include <TAxis.h>
10#include <TCanvas.h>
11#include <TCutG.h>
12#include <TFeldmanCousins.h>
13#include <TFile.h>
14#include <TGraph.h>
15#include <TGraphAsymmErrors.h>
16#include <TGraphErrors.h>
17#include <TH1F.h>
18#include <TH2F.h>
19#include <TKey.h>
20#include <TLatex.h>
21#include <TLegend.h>
22#include <TLine.h>
23#include <TMath.h>
24#include <TMatrixD.h>
25#include <TNamed.h>
26#include <TPad.h>
27#include <TParameter.h>
28#include <TStyle.h>
29#include <TSystem.h>
30#include <TVectorD.h>
31#include <algorithm>
32#include <cmath>
33#include <cstdio>
34#include <iostream>
35
36namespace {
37
38// Boltzmann constant [J/K] and Torr in Pa, for the ideal-gas number density.
39const Double_t kBoltzmann = 1.380649e-23;
40const Double_t kPaPerTorr = 133.322368;
41// MUSIC's geometry and fill temperature, the same for every dataset: the
42// active length of one anode strip along the beam, and room temperature.
43const Double_t kStripLengthCm = 1.578;
44const Double_t kGasTemperatureK = 293.0;
45// A barn is 1e-24 cm^2, so a millibarn is 1e-27 cm^2.
46const Double_t kCm2PerMb = 1.0e-27;
47
48// A tagged epoch's figures carry its tag, beside the untagged eras'.
49TString TagSuffix() {
50 const TString &tag = Constants::ActiveFileTag();
51 return tag.Length() > 0 ? "_" + tag : TString("");
52}
53
54// The curve's value at e, interpolated between the neighbouring grid points:
55// log-linear where both are positive (as the curve is drawn), else linear.
56Bool_t CurveAt(const TGraph *g, Double_t e, Double_t &v) {
57 for (Int_t p = 1; p < g->GetN(); p++) {
58 const Double_t x0 = g->GetX()[p - 1], x1 = g->GetX()[p];
59 if (!(x0 < e && e < x1))
60 continue;
61 const Double_t y0 = g->GetY()[p - 1], y1 = g->GetY()[p];
62 const Double_t t = (e - x0) / (x1 - x0);
63 v = (y0 > 0.0 && y1 > 0.0)
64 ? std::exp(std::log(y0) + t * (std::log(y1) - std::log(y0)))
65 : y0 + t * (y1 - y0);
66 return kTRUE;
67 }
68 return kFALSE;
69}
70
71// Marker styles and colours for the channels on a combined figure.
72const Int_t kChannelMarker[4] = {20, 21, 22, 23};
73const Int_t kChannelColor[4] = {kBlack, kGreen + 2, kMagenta + 2, kOrange + 7};
74// Colours for the model curves on a single-channel figure, one per TALYS
75// model in TALYS_MODELS order.
76const Int_t kModelColor[8] = {kAzure + 1, kRed + 1, kGreen + 2, kOrange + 7,
77 kMagenta + 1, kCyan + 2, kGray + 2, kViolet - 3};
78// Colours for the per-exit components on the fit figure, in exit order.
79const Int_t kComponentColor[4] = {kBlack, kBlue + 1, kGreen + 2, kMagenta + 1};
80
81} // namespace
82
83Double_t CrossSection::Point::ErrLo() const {
84 return std::sqrt(stat_lo * stat_lo + sys * sys);
85}
86
87Double_t CrossSection::Point::ErrHi() const {
88 return std::sqrt(stat_hi * stat_hi + sys * sys);
89}
90
91void CrossSection::PoissonInterval(Double_t count, Double_t &below,
92 Double_t &above) {
93 const Int_t n = Int_t(std::lround(TMath::Max(count, 0.0)));
94 if (n >= Constants::cfg.CROSS_SECTION_CONFIG.FELDMAN_COUSINS_MAX_COUNT) {
95 below = above = std::sqrt(count);
96 return;
97 }
98 // Feldman & Cousins, PRD 57 (1998) 3873, no background: the interval on
99 // mu whose likelihood-ratio-ordered 68.27% acceptance region holds n.
100 TFeldmanCousins fc(0.6827);
101 fc.SetMuMax(n + 10.0 * std::sqrt(n + 1.0) + 10.0);
102 fc.SetMuStep(0.005);
103 const Double_t hi = fc.CalculateUpperLimit(n, 0.0);
104 const Double_t lo = fc.GetLowerLimit();
105 below = n - lo;
106 above = hi - n;
107}
108
112Bool_t CrossSection::ExitResidue(const TString &exit, Int_t z_beam,
113 Int_t a_beam, Int_t &z, Int_t &a) {
114 Int_t dz = 0, da = 0, mult = 0;
115 for (Int_t i = 0; i < exit.Length(); i++) {
116 const char c = exit[i];
117 if (c >= '0' && c <= '9') {
118 mult = mult * 10 + (c - '0');
119 continue;
120 }
121 Int_t pz = 0, pa = 0;
122 switch (c) {
123 case 'n':
124 pz = 0;
125 pa = 1;
126 break;
127 case 'p':
128 pz = 1;
129 pa = 1;
130 break;
131 case 'd':
132 pz = 1;
133 pa = 2;
134 break;
135 case 't':
136 pz = 1;
137 pa = 3;
138 break;
139 case 'h':
140 pz = 2;
141 pa = 3;
142 break;
143 case 'a':
144 pz = 2;
145 pa = 4;
146 break;
147 case 'g':
148 pz = 0;
149 pa = 0;
150 break;
151 default:
152 return kFALSE;
153 }
154 const Int_t m = mult > 0 ? mult : 1;
155 dz += m * pz;
156 da += m * pa;
157 mult = 0;
158 }
159 if (mult > 0 || exit.Length() == 0)
160 return kFALSE;
161 z = z_beam + 2 - dz;
162 a = a_beam + 4 - da;
163 return z > 0 && a > z;
164}
165
167 if (ch.label.Length() > 0)
168 return ch.label;
169 if (ch.talys_exits.empty())
170 return "";
171 Bool_t all_neutron = kTRUE;
172 for (Int_t k = 0; k < Int_t(ch.talys_exits.size()); k++) {
173 TString e = ch.talys_exits[k];
174 e.ReplaceAll("n", "");
175 for (Int_t i = 0; i < e.Length(); i++)
176 if (e[i] < '0' || e[i] > '9')
177 all_neutron = kFALSE;
178 }
179 if (ch.talys_exits.size() == 1)
180 return "(#alpha, " + ch.talys_exits[0] + ")";
181 if (all_neutron)
182 return "(#alpha, xn)";
183 TString joined;
184 for (Int_t k = 0; k < Int_t(ch.talys_exits.size()); k++)
185 joined += (k ? ", " : "") + ch.talys_exits[k];
186 return "(#alpha, " + joined + ")";
187}
188
190 if (ch.subtract_exits.empty())
191 return "";
192 TString joined;
193 for (Int_t k = 0; k < Int_t(ch.subtract_exits.size()); k++)
194 joined += (k ? ", " : "") + ch.subtract_exits[k];
195 return "(#alpha, " + joined + ")";
196}
197
198Long64_t CrossSection::ReadCount(TFile &f, const char *name, Bool_t &ok) {
199 TParameter<Long64_t> *p = static_cast<TParameter<Long64_t> *>(f.Get(name));
200 if (!p) {
201 ok = kFALSE;
202 return 0;
203 }
204 return p->GetVal();
205}
206
207// The curve at e, log-linear between grid points where both are positive
208// (as it is drawn), zero outside the grid.
209Double_t CrossSection::Interpolated(TGraph *g, Double_t e) {
210 if (!g || g->GetN() < 2)
211 return 0.0;
212 for (Int_t p = 1; p < g->GetN(); p++) {
213 const Double_t x0 = g->GetX()[p - 1], x1 = g->GetX()[p];
214 if (!(x0 <= e && e <= x1))
215 continue;
216 const Double_t y0 = g->GetY()[p - 1], y1 = g->GetY()[p];
217 const Double_t t = (e - x0) / (x1 - x0);
218 return (y0 > 0.0 && y1 > 0.0)
219 ? std::exp(std::log(y0) + t * (std::log(y1) - std::log(y0)))
220 : y0 + t * (y1 - y0);
221 }
222 return 0.0;
223}
224
225// The curve clipped to an energy window, for drawing.
226TGraph *CrossSection::Clipped(TGraph *g, Double_t e_lo, Double_t e_hi) {
227 // The grid points inside the window, carried to its edges by
228 // interpolation so the curve reaches the frame.
229 std::vector<Double_t> x, y;
230 Double_t v = 0.0;
231 if (CurveAt(g, e_lo, v)) {
232 x.push_back(e_lo);
233 y.push_back(v);
234 }
235 for (Int_t p = 0; p < g->GetN(); p++)
236 if (g->GetX()[p] >= e_lo && g->GetX()[p] <= e_hi) {
237 x.push_back(g->GetX()[p]);
238 y.push_back(g->GetY()[p]);
239 }
240 if (CurveAt(g, e_hi, v)) {
241 x.push_back(e_hi);
242 y.push_back(v);
243 }
244 return x.empty() ? nullptr : new TGraph(Int_t(x.size()), &x[0], &y[0]);
245}
246
261Double_t CrossSection::EffectiveEnergy(TGraph *axn, Double_t e_out,
262 Double_t e_in) {
263 const Double_t mid = 0.5 * (e_out + e_in);
264 if (!axn || axn->GetN() < 2)
265 return mid;
266 const Double_t lo = TMath::Min(e_out, e_in), hi = TMath::Max(e_out, e_in);
267 if (lo < axn->GetX()[0] || hi > axn->GetX()[axn->GetN() - 1])
268 return mid;
269 const Double_t s1 = axn->Eval(hi), s2 = axn->Eval(lo);
270 if (s1 < 0.0 || s2 < 0.0 || s1 + s2 <= 0.0)
271 return mid;
272 const Double_t frac =
273 (s1 + s2) / (2.0 * (TMath::Sqrt(0.5 * (s1 * s1 + s2 * s2)) + s2));
274 return lo + frac * (hi - lo);
275}
276
277// The cut scaled about its own centroid; caller owns the copy. Scaling a
278// region drawn at N sigma by k puts it at kN sigma.
279TCutG *CrossSection::ScaledCut(TCutG *cut, Double_t scale) {
280 Double_t cx = 0.0, cy = 0.0;
281 for (Int_t p = 0; p < cut->GetN(); p++) {
282 cx += cut->GetX()[p];
283 cy += cut->GetY()[p];
284 }
285 cx /= Double_t(cut->GetN());
286 cy /= Double_t(cut->GetN());
287 TCutG *scaled = new TCutG(*cut);
288 for (Int_t p = 0; p < scaled->GetN(); p++)
289 scaled->SetPoint(p, cx + scale * (cut->GetX()[p] - cx),
290 cy + scale * (cut->GetY()[p] - cy));
291 return scaled;
292}
293
299Double_t CrossSection::CountInCut(TH2F *scatter, TCutG *cut, Double_t scale) {
300 TCutG *scaled = ScaledCut(cut, scale);
301 TAxis *ax = scatter->GetXaxis();
302 TAxis *ay = scatter->GetYaxis();
303 Double_t n = 0.0;
304 for (Int_t i = 1; i <= scatter->GetNbinsX(); i++)
305 for (Int_t j = 1; j <= scatter->GetNbinsY(); j++)
306 if (scaled->IsInside(ax->GetBinCenter(i), ay->GetBinCenter(j)))
307 n += scatter->GetBinContent(i, j);
308 delete scaled;
309 return n;
310}
311
312// Fraction of a bivariate Gaussian inside its n-sigma Mahalanobis ellipse.
313Double_t CrossSection::Enclosed(Double_t nsigma) {
314 return 1.0 - std::exp(-0.5 * nsigma * nsigma);
315}
316
317Double_t CrossSection::GasPressureSys(Double_t sigma) const {
318 const CrossSectionConfig &X = Constants::cfg.CROSS_SECTION_CONFIG;
319 if (!(X.GAS_PRESSURE_TORR_ERR > 0.0) || !(X.GAS_PRESSURE_TORR > 0.0))
320 return 0.0;
321 return sigma * X.GAS_PRESSURE_TORR_ERR / X.GAS_PRESSURE_TORR;
322}
323
324// The cut-variation systematic: per threshold the larger change of the
325// count under its up and down shift, in quadrature; `detail` lists them.
326Double_t CrossSection::CutVariation(Int_t reac, Double_t per_count,
327 TString &detail) const {
328 detail = "";
329 Bool_t at_ok = kTRUE;
330 const Long64_t n_nom = ReadCount(*cache_, Form("n_tagged_r%d", reac), at_ok);
331 Bool_t d_ok = kTRUE;
332 const Long64_t d_nom = ReadCount(*cache_, Form("n_normed_r%d", reac), d_ok);
333 TNamed *vn = static_cast<TNamed *>(cache_->Get("cut_variants"));
334 if (!at_ok || !d_ok || d_nom <= 0 || !vn)
335 return 0.0;
336 const Double_t s_nom = Double_t(n_nom) / Double_t(d_nom) * per_count;
337 std::map<TString, Double_t> worst;
338 std::map<TString, TString> sides;
339 TString names = vn->GetTitle(), tok;
340 Int_t from = 0;
341 while (names.Tokenize(tok, from, ",")) {
342 if (tok.IsNull())
343 continue;
344 Bool_t ok = kTRUE;
345 const Long64_t n_v =
346 ReadCount(*cache_, Form("n_tagged_r%d_%s", reac, tok.Data()), ok);
347 if (!ok)
348 continue;
349 // The variant's own denominator; a cache written before the variants
350 // carried one falls back to the nominal.
351 Bool_t dv_ok = kTRUE;
352 const Long64_t d_v =
353 ReadCount(*cache_, Form("n_normed_r%d_%s", reac, tok.Data()), dv_ok);
354 const Double_t denom = dv_ok && d_v > 0 ? Double_t(d_v) : Double_t(d_nom);
355 const TString base = tok(0, tok.Length() - 1);
356 const Double_t ds = Double_t(n_v) / denom * per_count - s_nom;
357 const Double_t d = std::fabs(ds);
358 if (d > worst[base])
359 worst[base] = d;
360 sides[base] += Form("%s%s %+.1f", sides[base].IsNull() ? "" : ", ",
361 tok.EndsWith("+") ? "up" : "down", ds);
362 }
363 Double_t var2 = 0.0;
364 for (std::map<TString, Double_t>::const_iterator it = worst.begin();
365 it != worst.end(); ++it) {
366 var2 += it->second * it->second;
367 detail += Form(" %s[%s]", it->first.Data(), sides[it->first].Data());
368 }
369 return std::sqrt(var2);
370}
371
372Bool_t CrossSection::LoadCache() {
373 const CrossSectionConfig &X = Constants::cfg.CROSS_SECTION_CONFIG;
374 TString path = IO::GetRootFilesBaseDir() + "/" + StripSumScatter::CacheName();
375 cache_ = new TFile(path, "READ");
376 if (cache_->IsZombie()) {
377 std::cerr << "cross-section: no scatter cache at " << path
378 << "; run strip-sum-scatter first" << std::endl;
379 return kFALSE;
380 }
381 Bool_t have = kTRUE;
382 n_seen_ = ReadCount(*cache_, "n_seen", have);
383 n_beam_ = ReadCount(*cache_, "n_normed", have);
384 if (!have || n_beam_ <= 0) {
385 std::cerr << "cross-section: the cache carries no normalization counts, so "
386 "it predates them; re-run strip-sum-scatter to refill"
387 << std::endl;
388 return kFALSE;
389 }
390 // Areal density of one strip: ideal gas at the fill conditions.
391 n_gas_ = X.GAS_PRESSURE_TORR * kPaPerTorr / (kBoltzmann * kGasTemperatureK) *
393 areal_ = n_gas_ * kStripLengthCm;
394 return kTRUE;
395}
396
401Bool_t CrossSection::LoadBeam() {
402 if (!BeamEnergies::Profile(BeamEnergies::SimPath(), dE_, e_strip0_)) {
403 std::cerr << "cross-section: cannot read the simulated beam at "
404 << BeamEnergies::SimPath() << std::endl;
405 return kFALSE;
406 }
407 cm_frac_ = BeamEnergies::CmFraction();
408 Double_t e = e_strip0_;
409 for (Int_t s = 0; s <= 16; s++) {
410 e_mid_[s] = (e - 0.5 * dE_[s]) * cm_frac_;
411 e -= dE_[s];
412 }
413 return kTRUE;
414}
415
421void CrossSection::LoadTalys() {
422 const CrossSectionConfig &X = Constants::cfg.CROSS_SECTION_CONFIG;
423 const TString path = Paths::ResultsDir() + "/root_files/talys/talys_xs.root";
424 if (gSystem->AccessPathName(path))
425 return;
426 TFile f(path, "READ");
427 if (f.IsZombie())
428 return;
429 for (Int_t m = 0;; m++) {
430 TDirectory *d = f.GetDirectory(Form("m%d", m));
431 if (!d)
432 break;
433 std::map<std::pair<Int_t, Int_t>, TGraph *> graphs;
434 for (TIter it(d->GetListOfKeys()); TObject *k = it();) {
435 Int_t z = 0, a = 0;
436 if (sscanf(k->GetName(), "rp%3d%3d", &z, &a) != 2)
437 continue;
438 TGraph *g = dynamic_cast<TGraph *>(d->Get(k->GetName()));
439 if (g)
440 graphs[std::make_pair(z, a)] = static_cast<TGraph *>(g->Clone());
441 }
442 TNamed *label = dynamic_cast<TNamed *>(d->Get("label"));
443 talys_labels_.push_back(m < Int_t(X.TALYS_MODELS.size())
444 ? X.TALYS_MODELS[m].label
445 : label ? TString(label->GetTitle())
446 : TString(Form("TALYS model %d", m)));
447 talys_raw_.push_back(graphs);
448 }
449}
450
451std::vector<CrossSection::TalysCurve>
452CrossSection::ExitCurves(const CrossSectionChannel &ch,
453 const std::vector<TString> &exits,
454 const char *what) const {
455 const CrossSectionConfig &X = Constants::cfg.CROSS_SECTION_CONFIG;
456 std::vector<TalysCurve> curves;
457 if (talys_raw_.empty() || exits.empty())
458 return curves;
459 for (Int_t m = 0; m < Int_t(talys_raw_.size()); m++) {
460 std::map<Double_t, Double_t> sum;
461 TString used, missing;
462 for (Int_t k = 0; k < Int_t(exits.size()); k++) {
463 Int_t z = 0, a = 0;
464 ExitResidue(exits[k], X.BEAM_Z, X.BEAM_A, z, a);
465 std::map<std::pair<Int_t, Int_t>, TGraph *>::const_iterator it =
466 talys_raw_[m].find(std::make_pair(z, a));
467 if (it == talys_raw_[m].end()) {
468 missing += Form(" %s(Z=%d,A=%d)", exits[k].Data(), z, a);
469 continue;
470 }
471 for (Int_t p = 0; p < it->second->GetN(); p++)
472 sum[it->second->GetX()[p]] += it->second->GetY()[p];
473 used += Form(" %s(Z=%d,A=%d)", exits[k].Data(), z, a);
474 }
475 std::vector<Double_t> tx, ty;
476 for (std::map<Double_t, Double_t>::const_iterator it = sum.begin();
477 it != sum.end(); ++it)
478 if (it->second > 0.0) {
479 tx.push_back(it->first);
480 ty.push_back(it->second);
481 }
482 std::cout << "cross-section: " << ch.name << ": " << talys_labels_[m]
483 << ": summed " << what << (used.Length() ? used : " nothing")
484 << (missing.Length() ? "; not in the file:" + missing : "")
485 << std::endl;
486 if (tx.empty())
487 continue;
488 TalysCurve c;
489 c.label = talys_labels_[m];
490 c.axn = new TGraph(Int_t(tx.size()), &tx[0], &ty[0]);
491 curves.push_back(c);
492 }
493 return curves;
494}
495
496Double_t CrossSection::StripAverage(TGraph *g, Double_t e_out, Double_t e_in,
497 Bool_t &covered) {
498 const Double_t lo = TMath::Min(e_out, e_in), hi = TMath::Max(e_out, e_in);
499 covered = g && g->GetN() >= 2 && lo >= g->GetX()[0] &&
500 hi <= g->GetX()[g->GetN() - 1];
501 const Int_t n_sub = 200;
502 Double_t sum = 0.0;
503 for (Int_t k = 0; k <= n_sub; k++) {
504 const Double_t e = lo + (hi - lo) * Double_t(k) / Double_t(n_sub);
505 sum += ((k == 0 || k == n_sub) ? 0.5 : 1.0) * Interpolated(g, e);
506 }
507 return sum / Double_t(n_sub);
508}
509
510Double_t
511CrossSection::SubtractedFraction(const std::vector<TalysCurve> &talys,
512 const std::vector<TalysCurve> &talys_sub,
513 Double_t e_out, Double_t e_in,
514 Double_t &spread) {
515 spread = 0.0;
516 if (talys_sub.empty() || talys.empty())
517 return 0.0;
518 Double_t sum = 0.0, lo = 1.0e9, hi = -1.0e9;
519 Int_t used = 0, skipped = 0;
520 // Pair kept and subtracted curves by model label.
521 for (Int_t ms = 0; ms < Int_t(talys_sub.size()); ms++) {
522 const TalysCurve *chan = nullptr;
523 for (Int_t m = 0; m < Int_t(talys.size()); m++)
524 if (talys[m].label == talys_sub[ms].label) {
525 chan = &talys[m];
526 break;
527 }
528 if (!chan) {
529 skipped++;
530 continue;
531 }
532 Bool_t cov_chan = kFALSE, cov_drop = kFALSE;
533 const Double_t keep = StripAverage(chan->axn, e_out, e_in, cov_chan);
534 const Double_t drop =
535 StripAverage(talys_sub[ms].axn, e_out, e_in, cov_drop);
536 if (!cov_chan || !cov_drop) {
537 skipped++;
538 continue;
539 }
540 if (!(keep + drop > 0.0))
541 continue;
542 const Double_t f = drop / (keep + drop);
543 sum += f;
544 lo = TMath::Min(lo, f);
545 hi = TMath::Max(hi, f);
546 used++;
547 }
548 if (used == 0) {
549 if (skipped > 0)
550 std::cout << Form(" no model both has the exits to subtract and "
551 "covers [%.2f, %.2f] MeV: nothing subtracted",
552 TMath::Min(e_out, e_in), TMath::Max(e_out, e_in))
553 << std::endl;
554 return 0.0;
555 }
556 spread = used > 1 ? 0.5 * (hi - lo) : 0.0;
557 return sum / Double_t(used);
558}
559
560Bool_t CrossSection::Strip(const CrossSectionChannel &ch,
561 const std::vector<TalysCurve> &talys,
562 const std::vector<TalysCurve> &talys_sub, Int_t reac,
563 Point &pt) {
564 const StripSumScatterConfig &C = Constants::cfg.STRIP_SUM_SCATTER_CONFIG;
565 const TString region = "region_" + ch.name;
566 TH2F *h = static_cast<TH2F *>(cache_->Get(Form("scatter_r%d", reac)));
567 TCutG *cut = RegionCutStore::Load(region, reac);
568 if (!h || !cut) {
569 std::cout << Form(" %2d no %s", reac,
570 h ? "region cut" : "scatter in the cache")
571 << std::endl;
572 delete cut;
573 return kFALSE;
574 }
575 pt.reac = reac;
576 // Beam particles reached this strip under the conditions a reaction here had
577 // to satisfy; flat-count fallback if the cache predates the per-strip one.
578 Bool_t at_ok = kTRUE;
579 const Long64_t n_at = ReadCount(*cache_, Form("n_normed_r%d", reac), at_ok);
580 pt.n_denom = Double_t(at_ok && n_at > 0 ? n_at : n_beam_);
581
582 // Strip entrance/exit energies: strip 0's energy, less each earlier strip's
583 // mean loss. e_eff from the first model; the others bound shape dependence.
584 Double_t e_lab = e_strip0_;
585 for (Int_t s = 0; s < reac; s++)
586 e_lab -= dE_[s];
587 pt.e_in = e_lab * cm_frac_;
588 pt.e_out = (e_lab - dE_[reac]) * cm_frac_;
590 pt.e_eff = use_eff ? EffectiveEnergy(talys.empty() ? nullptr : talys[0].axn,
591 pt.e_out, pt.e_in)
592 : 0.5 * (pt.e_in + pt.e_out);
593 pt.e_eff_lo = pt.e_eff_hi = pt.e_eff;
594 for (Int_t m = 1; use_eff && m < Int_t(talys.size()); m++) {
595 const Double_t e = EffectiveEnergy(talys[m].axn, pt.e_out, pt.e_in);
596 pt.e_eff_lo = TMath::Min(pt.e_eff_lo, e);
597 pt.e_eff_hi = TMath::Max(pt.e_eff_hi, e);
598 }
599 if (use_eff && talys.size() > 1)
600 std::cout << Form(" E_cm,eff %.3f (midpoint %.3f); across the %zu "
601 "model shapes %.3f..%.3f -> energy systematic "
602 "+%.3f/-%.3f",
603 pt.e_eff, 0.5 * (pt.e_in + pt.e_out), talys.size(),
604 pt.e_eff_lo, pt.e_eff_hi, pt.e_eff_hi - pt.e_eff,
605 pt.e_eff - pt.e_eff_lo)
606 << std::endl;
607
608 const Double_t norm = pt.n_denom * areal_ * kCm2PerMb;
609 const Bool_t all_tagged =
611 // Geometric = in-region / enclosed fraction; attributed = 3-sigma fit share.
612 // Disagreement = region systematic; geometric runs away over beam tail.
613 const Double_t n_raw = CountInCut(h, cut, 1.0);
614 // Remove the TALYS-predicted branching fraction from the tagged count.
615 pt.sub_frac =
616 SubtractedFraction(talys, talys_sub, pt.e_out, pt.e_in, pt.sub_frac_err);
617 const Double_t n_kept = n_raw * (1.0 - pt.sub_frac);
618 if (pt.sub_frac > 0.0)
619 std::cout << Form(" %s: TALYS puts %.2f%% +- %.2f%% of what is "
620 "tagged here in it, so %.0f of %.0f events are removed",
621 SubtractedLabel(ch).Data(), 100.0 * pt.sub_frac,
622 100.0 * pt.sub_frac_err, n_raw - n_kept, n_raw)
623 << std::endl;
624 if (all_tagged) {
625 // Every tagged event is the reaction: no enclosed-fraction correction,
626 // and the systematic is the cut variation plus the gas pressure.
627 pt.n_reac = n_kept;
628 pt.sigma = pt.n_reac / norm;
629 // Scale the count interval by the retained fraction.
630 Double_t below = 0.0, above = 0.0;
631 PoissonInterval(n_raw, below, above);
632 pt.stat_lo = below * (1.0 - pt.sub_frac) / norm;
633 pt.stat_hi = above * (1.0 - pt.sub_frac) / norm;
634 TString detail;
635 const Double_t sys_cuts =
636 CutVariation(reac, (1.0 - pt.sub_frac) / (areal_ * kCm2PerMb), detail);
637 const Double_t sys_gas = GasPressureSys(pt.sigma);
638 // Propagate the model spread on the removed fraction.
639 const Double_t sys_sub = pt.sub_frac_err * n_raw / norm;
640 pt.sys =
641 std::sqrt(sys_cuts * sys_cuts + sys_gas * sys_gas + sys_sub * sys_sub);
642 if (pt.sub_frac > 0.0)
643 std::cout << Form(" subtraction systematic %.2f mb (%.1f%% of "
644 "the point), against %.2f mb cut variation and %.2f "
645 "mb gas pressure",
646 sys_sub,
647 pt.sigma > 0.0 ? 100.0 * sys_sub / pt.sigma : 0.0,
648 sys_cuts, sys_gas)
649 << std::endl;
650 std::cout << Form(" %2d [%5.2f, %5.2f] %6.2f %6.0f %9.0f "
651 "%7.1f +%.1f -%.1f (+%.1f -%.1f stat%s, %.1f cut "
652 "variation, %.2f gas pressure%s; all tagged events "
653 "counted%s%s)",
654 reac, pt.e_in, pt.e_out, pt.e_eff, pt.n_reac, pt.n_denom,
655 pt.sigma, pt.ErrHi(), pt.ErrLo(), pt.stat_hi, pt.stat_lo,
658 ? " Feldman-Cousins"
659 : "",
660 sys_cuts, sys_gas,
661 pt.sub_frac > 0.0 ? Form(", %.2f subtraction", sys_sub)
662 : "",
663 detail.IsNull() ? "; no variation counts in the cache"
664 : "; per condition, change of sigma "
665 "with its threshold up (+) and "
666 "down (-):",
667 detail.Data())
668 << std::endl;
669 delete cut;
670 return kTRUE;
671 }
672 // Apply the same correction in the fitted-region mode.
673 const Double_t keep = 1.0 - pt.sub_frac;
674 const Double_t est_geo = n_raw * keep / Enclosed(C.AN_REGION_NSIGMA);
675 const Double_t n_assigned = RegionCutStore::LoadAssigned(region, reac);
676 const Bool_t have_fit = n_assigned >= 0.0;
677 const Double_t est_fit =
678 have_fit ? n_assigned * keep / Enclosed(3.0) : est_geo;
679 pt.n_reac = est_fit;
680 pt.sigma = pt.n_reac / norm;
681 {
682 // The Poisson count is what was counted; the point scales it by the
683 // enclosed fraction, so the interval scales with it.
684 const Double_t counted = have_fit ? n_assigned : n_raw;
685 const Double_t scale = counted > 0.0 ? pt.sigma / counted : 0.0;
686 Double_t below = 0.0, above = 0.0;
687 PoissonInterval(counted, below, above);
688 pt.stat_lo = below * scale;
689 pt.stat_hi = above * scale;
690 }
691 if (have_fit) {
692 // Core check: 1-sigma ellipse (half the drawn region), corrected for the
693 // Apply the retained fraction to the core count.
694 const Double_t est_core =
695 keep * CountInCut(h, cut, 1.0 / C.AN_REGION_NSIGMA) / Enclosed(1.0);
696 pt.sys = std::fabs(est_core - est_fit) / norm;
697 } else {
698 // A drawn region without an attributed count: scale it by 0.75x and
699 // Correct each variation for enclosure and subtraction.
700 Double_t lo = pt.sigma, hi = pt.sigma;
701 const Double_t kScale[2] = {0.75, 1.25};
702 for (Int_t k = 0; k < 2; k++) {
703 const Double_t s = keep * CountInCut(h, cut, kScale[k]) /
704 Enclosed(kScale[k] * C.AN_REGION_NSIGMA) / norm;
705 lo = TMath::Min(lo, s);
706 hi = TMath::Max(hi, s);
707 }
708 pt.sys = 0.5 * (hi - lo);
709 }
710 {
711 const Double_t sys_sub =
712 keep > 0.0 ? pt.sub_frac_err / keep * pt.sigma : 0.0;
713 pt.sys = std::sqrt(pt.sys * pt.sys +
714 GasPressureSys(pt.sigma) * GasPressureSys(pt.sigma) +
715 sys_sub * sys_sub);
716 }
717 std::cout << Form(" %2d [%5.2f, %5.2f] %6.2f %6.0f %9.0f "
718 "%7.1f +%.1f -%.1f (+%.1f -%.1f stat, %.1f region; "
719 "in-region %.0f, attributed %s)",
720 reac, pt.e_in, pt.e_out, pt.e_eff, pt.n_reac, pt.n_denom,
721 pt.sigma, pt.ErrHi(), pt.ErrLo(), pt.stat_hi, pt.stat_lo,
722 pt.sys, est_geo, have_fit ? Form("%.0f", est_fit) : "none")
723 << std::endl;
724 delete cut;
725 return kTRUE;
726}
727
733void CrossSection::CompareReference(const ChannelResult &r) const {
734 const std::vector<std::vector<Double_t>> &ref = r.ch->reference_xs;
735 if (ref.empty())
736 return;
737 const Int_t n_ref = Int_t(ref.size());
738 Int_t best_off = 0;
739 Double_t best_rms = 1.0e9;
740 for (Int_t off = 0; off + n_ref - 1 <= 16; off++) {
741 Double_t s = 0.0;
742 for (Int_t k = 0; k < n_ref; k++) {
743 const Double_t d = e_mid_[off + k] - ref[k][0];
744 s += d * d;
745 }
746 const Double_t rms = std::sqrt(s / Double_t(n_ref));
747 if (rms < best_rms) {
748 best_rms = rms;
749 best_off = off;
750 }
751 }
752 std::cout << std::endl;
753 std::cout << Form(" %s vs %s: first row is strip %d from the energies, rms "
754 "%.3f MeV",
755 r.ch->name.Data(), r.ch->reference_label.Data(), best_off,
756 best_rms)
757 << std::endl;
758 std::cout << " strip E_cm,eff published E this work [mb] "
759 "published [mb] ratio"
760 << std::endl;
761 for (Int_t i = 0; i < Int_t(r.points.size()); i++) {
762 const Point &pt = r.points[i];
763 const Int_t row = pt.reac - best_off;
764 if (row < 0 || row >= n_ref) {
765 std::cout << Form(" %2d %6.2f -- %7.1f "
766 " -- (upstream of the published range)",
767 pt.reac, pt.e_eff, pt.sigma)
768 << std::endl;
769 continue;
770 }
771 const Double_t v = ref[row][3];
772 std::cout << Form(" %2d %6.2f %6.2f %7.1f +%-5.1f -%-5.1f "
773 "%7.1f (%.1f) %5.2f",
774 pt.reac, pt.e_eff, ref[row][0], pt.sigma, pt.ErrHi(),
775 pt.ErrLo(), v, ref[row][4], v > 0.0 ? pt.sigma / v : 0.0)
776 << std::endl;
777 }
778}
779
784void CrossSection::Draw(const std::vector<const ChannelResult *> &rs,
785 const TString &name) const {
786 Double_t fx_lo = 1.0e9, fx_hi = -1.0e9, fy_lo = 1.0e9, fy_hi = -1.0e9;
787 struct Series {
788 TGraphAsymmErrors *g;
789 TString label;
790 };
791 std::vector<Series> measured, published;
792 std::vector<std::pair<TGraph *, TString>> curves;
793 for (Int_t c = 0; c < Int_t(rs.size()); c++) {
794 const ChannelResult &r = *rs[c];
795 std::vector<Double_t> vx, vy, vexl, vexh, veyl, veyh;
796 for (Int_t i = 0; i < Int_t(r.points.size()); i++) {
797 const Point &pt = r.points[i];
798 // Energy error = strip extent about e_eff, asymmetric (above the midpoint
799 // on a rising curve); e_eff's shape dependence added in quadrature.
800 vx.push_back(pt.e_eff);
801 vy.push_back(pt.sigma);
802 vexl.push_back(std::hypot(pt.e_eff - TMath::Min(pt.e_in, pt.e_out),
803 pt.e_eff - pt.e_eff_lo));
804 vexh.push_back(std::hypot(TMath::Max(pt.e_in, pt.e_out) - pt.e_eff,
805 pt.e_eff_hi - pt.e_eff));
806 veyl.push_back(pt.ErrLo());
807 veyh.push_back(pt.ErrHi());
808 fx_lo = TMath::Min(fx_lo, vx.back() - vexl.back());
809 fx_hi = TMath::Max(fx_hi, vx.back() + vexh.back());
810 fy_lo = TMath::Min(fy_lo, vy.back());
811 fy_hi = TMath::Max(fy_hi, vy.back());
812 }
813 if (vx.empty())
814 continue;
815 Series s;
816 s.g = new TGraphAsymmErrors(Int_t(vx.size()), &vx[0], &vy[0], &vexl[0],
817 &vexh[0], &veyl[0], &veyh[0]);
818 s.g->SetMarkerStyle(kChannelMarker[c % 4]);
819 s.g->SetMarkerColor(kChannelColor[c % 4]);
820 s.g->SetLineColor(kChannelColor[c % 4]);
821 s.label = rs.size() > 1 ? "Present Work " + Label(*r.ch) : "Present Work";
822 measured.push_back(s);
823 const std::vector<std::vector<Double_t>> &ref = r.ch->reference_xs;
824 if (!ref.empty()) {
825 std::vector<Double_t> rx, ry, rexl, rexh, rey;
826 for (Int_t k = 0; k < Int_t(ref.size()); k++) {
827 rx.push_back(ref[k][0]);
828 rexh.push_back(ref[k][1]);
829 rexl.push_back(ref[k][2]);
830 ry.push_back(ref[k][3]);
831 rey.push_back(ref[k][4]);
832 fx_lo = TMath::Min(fx_lo, ref[k][0]);
833 fx_hi = TMath::Max(fx_hi, ref[k][0]);
834 fy_lo = TMath::Min(fy_lo, ref[k][3]);
835 fy_hi = TMath::Max(fy_hi, ref[k][3]);
836 }
837 Series p;
838 p.g = new TGraphAsymmErrors(Int_t(rx.size()), &rx[0], &ry[0], &rexl[0],
839 &rexh[0], &rey[0], &rey[0]);
840 p.g->SetMarkerStyle(24 + (c % 4));
841 p.g->SetMarkerColor(kRed + 1);
842 p.g->SetLineColor(kRed + 1);
843 p.label = rs.size() > 1 ? r.ch->reference_label + " " + Label(*r.ch)
844 : r.ch->reference_label;
845 published.push_back(p);
846 }
847 // Hauser-Feshbach prediction, if talys-xs wrote one, one colour per
848 // model; combined figure: first per channel only, in its colour.
849 for (Int_t m = 0; m < Int_t(r.talys.size()); m++) {
850 if (rs.size() > 1 && m > 0)
851 break;
852 curves.push_back(std::make_pair(
853 r.talys[m].axn, rs.size() > 1 ? r.talys[m].label + " " + Label(*r.ch)
854 : r.talys[m].label));
855 }
856 }
857 if (measured.empty())
858 return;
859 TCanvas *c = PlottingUtils::GetConfiguredCanvas(kTRUE);
860 TH1F *frame =
861 c->DrawFrame(fx_lo - 0.4, 0.5 * fy_lo, fx_hi + 0.4, 3.0 * fy_hi);
862 frame->SetTitle(Form("%s%s;%s [MeV];#sigma [mb]", Paths::DatasetName().Data(),
863 rs.size() == 1 ? Label(*rs[0]->ch).Data() : "",
864 Constants::cfg.CROSS_SECTION_CONFIG.EFFECTIVE_ENERGY
865 ? "E_{c.m.,eff}"
866 : "E_{c.m.}"));
867 // Curves first so the points sit on top, and only over the frame's
868 // energies.
869 std::vector<std::pair<TGraph *, TString>> drawn;
870 for (Int_t k = 0; k < Int_t(curves.size()); k++) {
871 TGraph *g = Clipped(curves[k].first, fx_lo - 0.4, fx_hi + 0.4);
872 if (!g)
873 continue;
874 // Combined figure: the channel's colour. One channel: a colour per
875 // model. All dashed: the colour is the distinction, the dash says model.
876 const Int_t ch = rs.size() > 1 ? k : 0;
877 g->SetLineColor(rs.size() > 1 ? kChannelColor[ch % 4] : kModelColor[k % 8]);
878 g->SetLineWidth(2);
879 g->SetLineStyle(2);
880 g->Draw("L SAME");
881 drawn.push_back(std::make_pair(g, curves[k].second));
882 }
883 for (Int_t k = 0; k < Int_t(published.size()); k++)
884 published[k].g->Draw("P SAME");
885 for (Int_t k = 0; k < Int_t(measured.size()); k++)
886 measured[k].g->Draw("P SAME");
887 // Bottom right is the one empty corner: the excitation function climbs to
888 // the upper right and the reference table starts at the lower left.
889 const Int_t n_entries =
890 Int_t(measured.size() + published.size() + drawn.size());
891 TLegend *leg =
892 PlottingUtils::AddLegend(0.42, 0.89, 0.16, 0.18 + 0.048 * n_entries);
893 for (Int_t k = 0; k < Int_t(measured.size()); k++)
894 leg->AddEntry(measured[k].g, measured[k].label, "pe");
895 for (Int_t k = 0; k < Int_t(published.size()); k++)
896 leg->AddEntry(published[k].g, published[k].label, "pe");
897 for (Int_t k = 0; k < Int_t(drawn.size()); k++)
898 leg->AddEntry(drawn[k].first, drawn[k].second, "l");
899 leg->Draw();
900 // The stamp, drawn last so it sits over the frame.
901 StampPreliminary(c);
902 PlottingUtils::SaveFigure(c, name, "cross_section", PlotSaveOptions::kLOG);
903 delete c;
904}
905
906void CrossSection::StampPreliminary(const TPad *pad) {
907 // The stamp: red, bold, in the top-left corner of the plot area (just
908 // inside the frame's margins).
909 if (!Constants::cfg.CROSS_SECTION_CONFIG.PRELIMINARY)
910 return;
911 TLatex *stamp = new TLatex();
912 stamp->SetNDC();
913 stamp->SetTextAlign(13); // left, top
914 stamp->SetTextSize(0.05);
915 stamp->SetTextFont(62);
916 stamp->SetTextColor(kRed + 1);
917 stamp->DrawLatex(pad->GetLeftMargin() + 0.02,
918 1.0 - pad->GetTopMargin() - 0.02, "PRELIMINARY");
919}
920
921Bool_t CrossSection::CollectFitComponents(const ChannelResult &r, Int_t m,
922 FitComponents &comp) const {
923 const CrossSectionConfig &X = Constants::cfg.CROSS_SECTION_CONFIG;
924 const CrossSectionChannel &ch = *r.ch;
925 for (Int_t k = 0; k < Int_t(ch.talys_exits.size()); k++) {
926 Int_t z = 0, a = 0;
927 ExitResidue(ch.talys_exits[k], X.BEAM_Z, X.BEAM_A, z, a);
928 std::map<std::pair<Int_t, Int_t>, TGraph *>::const_iterator it =
929 talys_raw_[m].find(std::make_pair(z, a));
930 if (it == talys_raw_[m].end())
931 continue;
932 comp.graph.push_back(it->second);
933 comp.label.push_back("(#alpha, " + ch.talys_exits[k] + ")");
934 comp.tag.push_back(ch.talys_exits[k]);
935 }
936 comp.n_keep = comp.Size();
937 // Include subtracted exits as free fit components.
938 for (Int_t k = 0; k < Int_t(ch.subtract_exits.size()); k++) {
939 Int_t z = 0, a = 0;
940 ExitResidue(ch.subtract_exits[k], X.BEAM_Z, X.BEAM_A, z, a);
941 std::map<std::pair<Int_t, Int_t>, TGraph *>::const_iterator it =
942 talys_raw_[m].find(std::make_pair(z, a));
943 if (it == talys_raw_[m].end())
944 continue;
945 comp.graph.push_back(it->second);
946 comp.label.push_back("(#alpha, " + ch.subtract_exits[k] + ") subtracted");
947 comp.tag.push_back(ch.subtract_exits[k]);
948 }
949 if (comp.n_keep == 0) {
950 std::cerr << "cross-section: channel " << ch.name << ": "
951 << talys_labels_[m] << " has none of the channel's exits"
952 << std::endl;
953 return kFALSE;
954 }
955 return kTRUE;
956}
957
958void CrossSection::FitExitScales(
959 const ChannelResult &r, const FitComponents &comp, ScaleFit &fs,
960 const std::vector<Double_t> *point_scale) const {
961 const Int_t nc = comp.Size();
962 const Int_t np = Int_t(r.points.size());
963
964 // Weighted linear least squares for the scales, dropping exits that are
965 // zero at every point (their scale is undetermined).
966 for (Int_t k = 0; k < nc; k++) {
967 Bool_t any = kFALSE;
968 for (Int_t i = 0; i < np && !any; i++)
969 any = Interpolated(comp.graph[k], r.points[i].e_eff) > 0.0;
970 if (any)
971 fs.free_idx.push_back(k);
972 }
973 fs.scale.assign(nc, 1.0);
974 fs.scale_err.assign(nc, 0.0);
975 fs.scale_raw.assign(nc, 1.0);
976 fs.scale_raw_err.assign(nc, 0.0);
977 Int_t nf = Int_t(fs.free_idx.size());
978 fs.cov.ResizeTo(nf, nf);
979 Bool_t first_pass = kTRUE;
980 while (nf > 0 && np >= nf) {
981 TMatrixD mat(nf, nf);
982 TVectorD rhs(nf);
983 for (Int_t i = 0; i < np; i++) {
984 const Point &pt = r.points[i];
985 const Double_t k = point_scale ? (*point_scale)[i] : 1.0;
986 const Double_t err = k * 0.5 * (pt.ErrLo() + pt.ErrHi());
987 if (!(err > 0.0))
988 continue;
989 const Double_t w = 1.0 / (err * err);
990 for (Int_t a = 0; a < nf; a++) {
991 const Double_t ca = Interpolated(comp.graph[fs.free_idx[a]], pt.e_eff);
992 rhs[a] += w * ca * k * pt.sigma;
993 for (Int_t b = 0; b < nf; b++)
994 mat[a][b] +=
995 w * ca * Interpolated(comp.graph[fs.free_idx[b]], pt.e_eff);
996 }
997 }
998 fs.cov.ResizeTo(nf, nf);
999 fs.cov = mat;
1000 fs.cov.Invert();
1001 const TVectorD sol = fs.cov * rhs;
1002 // Preserve the raw solution before nonnegative pinning.
1003 if (first_pass) {
1004 for (Int_t a = 0; a < nf; a++) {
1005 fs.scale_raw[fs.free_idx[a]] = sol[a];
1006 fs.scale_raw_err[fs.free_idx[a]] =
1007 std::sqrt(TMath::Max(0.0, fs.cov[a][a]));
1008 }
1009 first_pass = kFALSE;
1010 }
1011 Int_t worst = -1;
1012 for (Int_t a = 0; a < nf; a++)
1013 if (sol[a] < 0.0 && (worst < 0 || sol[a] < sol[worst]))
1014 worst = a;
1015 if (worst >= 0) {
1016 fs.pinned.push_back(fs.free_idx[worst]);
1017 fs.scale[fs.free_idx[worst]] = 0.0;
1018 fs.free_idx.erase(fs.free_idx.begin() + worst);
1019 nf--;
1020 continue;
1021 }
1022 for (Int_t a = 0; a < nf; a++) {
1023 fs.scale[fs.free_idx[a]] = sol[a];
1024 fs.scale_err[fs.free_idx[a]] = std::sqrt(TMath::Max(0.0, fs.cov[a][a]));
1025 }
1026 break;
1027 }
1028 fs.nf = nf;
1029 for (Int_t i = 0; i < np; i++) {
1030 const Point &pt = r.points[i];
1031 const Double_t k = point_scale ? (*point_scale)[i] : 1.0;
1032 const Double_t err = k * 0.5 * (pt.ErrLo() + pt.ErrHi());
1033 Double_t f = 0.0;
1034 for (Int_t c = 0; c < nc; c++)
1035 f += fs.scale[c] * Interpolated(comp.graph[c], pt.e_eff);
1036 if (err > 0.0)
1037 fs.chi2 += std::pow((k * pt.sigma - f) / err, 2);
1038 }
1039}
1040
1041void CrossSection::PrintScaleFit(const CrossSectionChannel &ch, Int_t m,
1042 const FitComponents &comp, const ScaleFit &fs,
1043 Int_t np) const {
1044 const Int_t nc = comp.Size();
1045 std::cout << std::endl
1046 << " " << ch.name << " " << Label(ch) << ": " << talys_labels_[m]
1047 << " scaled to this work's points" << std::endl;
1048 for (Int_t k = 0; k < nc; k++) {
1049 const Bool_t is_pinned =
1050 std::find(fs.pinned.begin(), fs.pinned.end(), k) != fs.pinned.end();
1051 std::cout << Form(" %-26s scale %.4f +- %.4f%s", comp.label[k].Data(),
1052 fs.scale[k], fs.scale_err[k],
1053 is_pinned ? " (went negative: pinned at 0)"
1054 : fs.scale_err[k] > 0.0
1055 ? ""
1056 : " (zero at every point: fixed)")
1057 << std::endl;
1058 }
1059 for (Int_t a = 0; a < fs.nf; a++)
1060 for (Int_t b = a + 1; b < fs.nf; b++)
1061 std::cout << Form(" correlation %s-%s %.3f",
1062 comp.label[fs.free_idx[a]].Data(),
1063 comp.label[fs.free_idx[b]].Data(),
1064 fs.cov[a][b] / std::sqrt(fs.cov[a][a] * fs.cov[b][b]))
1065 << std::endl;
1066 std::cout << Form(" chi2 %.2f for %d points, %d free scale(s)", fs.chi2,
1067 np, fs.nf)
1068 << std::endl;
1069}
1070
1072void CrossSection::PrintSubtractionCheck(const ChannelResult &r,
1073 const FitComponents &comp,
1074 const ScaleFit &fs) const {
1075 if (comp.n_keep >= comp.Size())
1076 return;
1077 const Int_t np = Int_t(r.points.size());
1078 // Undo the correction for the comparison fit.
1079 std::vector<Double_t> undo(np, 1.0);
1080 Bool_t have_undo = kTRUE;
1081 for (Int_t i = 0; i < np; i++) {
1082 const Double_t keep = 1.0 - r.points[i].sub_frac;
1083 if (!(keep > 0.0)) {
1084 have_undo = kFALSE;
1085 break;
1086 }
1087 undo[i] = 1.0 / keep;
1088 }
1089 ScaleFit before;
1090 if (have_undo)
1091 FitExitScales(r, comp, before, &undo);
1092
1093 std::cout << " subtraction check: the subtracted exits' scales with the "
1094 "points as measured and as corrected"
1095 << std::endl;
1096 for (Int_t k = comp.n_keep; k < comp.Size(); k++) {
1097 const Double_t after = fs.scale_raw[k], after_e = fs.scale_raw_err[k];
1098 if (!have_undo) {
1099 std::cout << Form(" %-26s after %.3f +- %.3f", comp.label[k].Data(),
1100 after, after_e)
1101 << std::endl;
1102 continue;
1103 }
1104 // The expected change is the kept-channel scale.
1105 const Double_t raw = before.scale_raw[k];
1106 const Double_t expected = comp.n_keep > 0 ? -before.scale_raw[0] : -1.0;
1107 std::cout << Form(" %-26s before %.3f, after %.3f (+- %.3f): moved "
1108 "%+.3f, expected %+.3f",
1109 comp.label[k].Data(), raw, after, after_e, after - raw,
1110 expected)
1111 << std::endl;
1112 }
1113 // Report whether the component fit is informative.
1114 Double_t rho = 0.0;
1115 for (Int_t a = 0; a < fs.nf; a++)
1116 for (Int_t b = a + 1; b < fs.nf; b++)
1117 if (fs.cov[a][a] > 0.0 && fs.cov[b][b] > 0.0) {
1118 const Double_t c =
1119 fs.cov[a][b] / std::sqrt(fs.cov[a][a] * fs.cov[b][b]);
1120 if (std::fabs(c) > std::fabs(rho))
1121 rho = c;
1122 }
1123 const Double_t chi2_ndf =
1124 np > fs.nf ? fs.chi2 / Double_t(np - fs.nf) : fs.chi2;
1125 const Bool_t degenerate = std::fabs(rho) > 0.9;
1126 const Bool_t poor_fit = chi2_ndf > 5.0;
1127 if (degenerate || poor_fit)
1128 std::cout << Form(" the scale is not a usable check here: %s%s%s. A "
1129 "component this close to the channel's own absorbs the "
1130 "shape mismatch rather than its own yield, so its value "
1131 "says nothing about how much %s is left. The "
1132 "subtraction rests on the TALYS branching, and its "
1133 "uncertainty is the model spread already in the "
1134 "systematic.",
1135 degenerate
1136 ? Form("the two scales are %.2f correlated", rho)
1137 : "",
1138 degenerate && poor_fit ? " and " : "",
1139 poor_fit ? Form("chi2/ndf is %.1f, so the model does not "
1140 "describe the shape",
1141 chi2_ndf)
1142 : "",
1143 SubtractedLabel(*r.ch).Data())
1144 << std::endl;
1145 else
1146 std::cout << Form(" chi2/ndf %.1f and correlation %.2f, so the move "
1147 "is meaningful",
1148 chi2_ndf, rho)
1149 << std::endl;
1150}
1151
1152Bool_t CrossSection::BuildScaledCurves(const ChannelResult &r,
1153 const FitComponents &comp,
1154 const ScaleFit &fs, TGraph *&sum,
1155 TGraph *&fit,
1156 TGraphErrors *&band) const {
1157 const Int_t nc = comp.Size();
1158 // The scaled sum and its 3-sigma band on the model's grid, and the
1159 // The unscaled sum contains kept exits only.
1160 std::vector<Double_t> gx, gsum, gfit, gband;
1161 for (Int_t p = 0; p < comp.graph[0]->GetN(); p++) {
1162 const Double_t e = comp.graph[0]->GetX()[p];
1163 Double_t s = 0.0, f = 0.0, var = 0.0;
1164 std::vector<Double_t> c(nc);
1165 for (Int_t k = 0; k < nc; k++) {
1166 c[k] = Interpolated(comp.graph[k], e);
1167 if (k < comp.n_keep)
1168 s += c[k];
1169 f += fs.scale[k] * c[k];
1170 }
1171 for (Int_t a = 0; a < fs.nf; a++)
1172 for (Int_t b = 0; b < fs.nf; b++)
1173 var += c[fs.free_idx[a]] * c[fs.free_idx[b]] * fs.cov[a][b];
1174 if (!(s > 0.0))
1175 continue;
1176 gx.push_back(e);
1177 gsum.push_back(s);
1178 gfit.push_back(f);
1179 gband.push_back(3.0 * std::sqrt(TMath::Max(0.0, var)));
1180 }
1181 if (gx.empty())
1182 return kFALSE;
1183 sum = new TGraph(Int_t(gx.size()), &gx[0], &gsum[0]);
1184 fit = new TGraph(Int_t(gx.size()), &gx[0], &gfit[0]);
1185 std::vector<Double_t> zero(gx.size(), 0.0);
1186 band =
1187 new TGraphErrors(Int_t(gx.size()), &gx[0], &gfit[0], &zero[0], &gband[0]);
1188 return kTRUE;
1189}
1190
1191void CrossSection::BuildMeasuredGraphs(const ChannelResult &r, TGraph *fit,
1192 TGraphAsymmErrors *&measured,
1193 TGraphAsymmErrors *&deviation,
1194 FitFrame &frame) const {
1195 const Int_t np = Int_t(r.points.size());
1196 // The measured points and the deviation of each point from the scaled
1197 // sum, printed as a row.
1198 std::vector<Double_t> vx, vy, vexl, vexh, veyl, veyh, dy, deyl, deyh;
1199 for (Int_t i = 0; i < np; i++) {
1200 const Point &pt = r.points[i];
1201 vx.push_back(pt.e_eff);
1202 vy.push_back(pt.sigma);
1203 vexl.push_back(std::hypot(pt.e_eff - TMath::Min(pt.e_in, pt.e_out),
1204 pt.e_eff - pt.e_eff_lo));
1205 vexh.push_back(std::hypot(TMath::Max(pt.e_in, pt.e_out) - pt.e_eff,
1206 pt.e_eff_hi - pt.e_eff));
1207 veyl.push_back(pt.ErrLo());
1208 veyh.push_back(pt.ErrHi());
1209 frame.fx_lo = TMath::Min(frame.fx_lo, vx.back() - vexl.back());
1210 frame.fx_hi = TMath::Max(frame.fx_hi, vx.back() + vexh.back());
1211 frame.fy_lo = TMath::Min(frame.fy_lo, vy.back());
1212 frame.fy_hi = TMath::Max(frame.fy_hi, vy.back());
1213 const Double_t f = Interpolated(fit, pt.e_eff);
1214 const Double_t err = pt.sigma > f ? pt.ErrLo() : pt.ErrHi();
1215 const Double_t pull = err > 0.0 ? (pt.sigma - f) / err : 0.0;
1216 dy.push_back(pull);
1217 deyl.push_back(1.0);
1218 deyh.push_back(1.0);
1219 frame.pull_max = TMath::Max(frame.pull_max, std::abs(pull) + 1.0);
1220 std::cout << Form(" %2d %6.2f %8.2f %8.2f "
1221 "%+6.2f",
1222 pt.reac, pt.e_eff, pt.sigma, f, pull)
1223 << std::endl;
1224 }
1225 measured = new TGraphAsymmErrors(np, &vx[0], &vy[0], &vexl[0], &vexh[0],
1226 &veyl[0], &veyh[0]);
1227 measured->SetMarkerStyle(kChannelMarker[0]);
1228 measured->SetMarkerColor(kChannelColor[0]);
1229 measured->SetLineColor(kChannelColor[0]);
1230 deviation = new TGraphAsymmErrors(np, &vx[0], &dy[0], &vexl[0], &vexh[0],
1231 &deyl[0], &deyh[0]);
1232 deviation->SetMarkerStyle(kChannelMarker[0]);
1233 deviation->SetMarkerColor(kChannelColor[0]);
1234 deviation->SetLineColor(kChannelColor[0]);
1235}
1236
1237TGraphAsymmErrors *
1238CrossSection::BuildPublishedGraph(const CrossSectionChannel &ch,
1239 FitFrame &frame) const {
1240 TGraphAsymmErrors *published = nullptr;
1241 const std::vector<std::vector<Double_t>> &ref = ch.reference_xs;
1242 if (!ref.empty()) {
1243 std::vector<Double_t> rx, ry, rexl, rexh, rey;
1244 for (Int_t k = 0; k < Int_t(ref.size()); k++) {
1245 rx.push_back(ref[k][0]);
1246 rexh.push_back(ref[k][1]);
1247 rexl.push_back(ref[k][2]);
1248 ry.push_back(ref[k][3]);
1249 rey.push_back(ref[k][4]);
1250 frame.fx_lo = TMath::Min(frame.fx_lo, ref[k][0]);
1251 frame.fx_hi = TMath::Max(frame.fx_hi, ref[k][0]);
1252 frame.fy_lo = TMath::Min(frame.fy_lo, ref[k][3]);
1253 frame.fy_hi = TMath::Max(frame.fy_hi, ref[k][3]);
1254 }
1255 published = new TGraphAsymmErrors(Int_t(rx.size()), &rx[0], &ry[0],
1256 &rexl[0], &rexh[0], &rey[0], &rey[0]);
1257 published->SetMarkerStyle(24);
1258 published->SetMarkerColor(kRed + 1);
1259 published->SetLineColor(kRed + 1);
1260 }
1261 return published;
1262}
1263
1264void CrossSection::FitLegendWidthPx(const ScaleFit &fs,
1265 const CrossSectionChannel &ch,
1266 const FitComponents &comp,
1267 const TString &model_label,
1268 Bool_t has_published, TString &scale_text,
1269 Double_t &legend_width_px) const {
1270 scale_text = "#times";
1271 for (Int_t k = 0; k < comp.Size(); k++)
1272 scale_text +=
1273 Form("%s %.2f_{%s}", k ? " &" : "", fs.scale[k], comp.tag[k].Data());
1274 std::vector<TString> legend_labels;
1275 legend_labels.push_back("Present Work");
1276 if (has_published)
1277 legend_labels.push_back(ch.reference_label);
1278 legend_labels.push_back(Label(ch) + " scaled");
1279 legend_labels.push_back(scale_text);
1280 legend_labels.push_back(Label(ch));
1281 if (comp.Size() > 1)
1282 for (Int_t k = 0; k < comp.Size(); k++)
1283 legend_labels.push_back(comp.label[k]);
1284 legend_width_px = PlottingUtils::LegendWidthPx(legend_labels, model_label);
1285}
1286
1287void CrossSection::DrawFitTopPanel(
1288 const CrossSectionChannel &ch, const FitFrame &frame,
1289 const TString &scale_text, Double_t legend_width_px, TGraph *sum,
1290 TGraph *fit, TGraphErrors *band, const FitComponents &comp,
1291 TGraphAsymmErrors *published, TGraphAsymmErrors *measured,
1292 FitPanels &pads) const {
1293 const Double_t x_lo = frame.fx_lo - 0.4, x_hi = frame.fx_hi + 0.4;
1294 const Int_t nc = comp.Size();
1295 const Int_t panel_height_px = 205;
1296 const Int_t plot_right_margin_px =
1297 TMath::Nint(1200.0 * gStyle->GetPadRightMargin());
1298 const Double_t legend_gap_px = 40.0;
1299 const Int_t extra_width_px =
1300 TMath::Max(0, TMath::Nint(legend_width_px + 2.0 * legend_gap_px) -
1301 plot_right_margin_px);
1302 TCanvas *c = PlottingUtils::GetConfiguredCanvasWithSideLegend(
1303 pads.plot, pads.side, extra_width_px, 800 + panel_height_px, kTRUE);
1304 TPad *top = nullptr;
1305 TPad *bottom = nullptr;
1306 PlottingUtils::SplitPadForPanel(pads.plot, panel_height_px, top, bottom);
1307 pads.c = c;
1308 pads.top = top;
1309 pads.bottom = bottom;
1310 pads.plot_right_margin_px = plot_right_margin_px;
1311 pads.extra_width_px = extra_width_px;
1312
1313 pads.top->cd();
1314 TH1F *frame_hist =
1315 pads.top->DrawFrame(x_lo, 0.5 * frame.fy_lo, x_hi, 3.0 * frame.fy_hi);
1316 frame_hist->SetTitle(";;#sigma [mb]");
1317 frame_hist->GetXaxis()->SetLabelSize(0);
1318 frame_hist->GetXaxis()->SetTitleSize(0);
1319
1320 {
1321 std::vector<Double_t> bx, by, bz, be;
1322 for (Int_t p = 0; p < band->GetN(); p++)
1323 if (band->GetX()[p] >= x_lo && band->GetX()[p] <= x_hi) {
1324 bx.push_back(band->GetX()[p]);
1325 by.push_back(band->GetY()[p]);
1326 bz.push_back(0.0);
1327 be.push_back(band->GetEY()[p]);
1328 }
1329 if (!bx.empty()) {
1330 TGraphErrors *band_c =
1331 new TGraphErrors(Int_t(bx.size()), &bx[0], &by[0], &bz[0], &be[0]);
1332 band_c->SetFillColorAlpha(kRed + 1, 0.25);
1333 band_c->SetLineWidth(0);
1334 band_c->Draw("3 SAME");
1335 }
1336 }
1337 if (TGraph *g = Clipped(fit, x_lo, x_hi)) {
1338 PlottingUtils::ConfigureGraph(g, kRed + 1);
1339 g->Draw("L SAME");
1340 pads.entries.push_back(std::make_pair(g, Label(ch) + " scaled"));
1341 pads.entries.push_back(std::make_pair((TObject *)nullptr, scale_text));
1342 }
1343 if (TGraph *g = Clipped(sum, x_lo, x_hi)) {
1344 PlottingUtils::ConfigureGraph(g, kBlack);
1345 g->Draw("L SAME");
1346 pads.entries.push_back(std::make_pair(g, Label(ch)));
1347 }
1348 if (nc > 1)
1349 for (Int_t k = 0; k < nc; k++) {
1350 TGraph *g = Clipped(comp.graph[k], x_lo, x_hi);
1351 if (!g)
1352 continue;
1353 PlottingUtils::ConfigureGraph(g, kComponentColor[k % 4]);
1354 // Subtracted exits are dotted and grey.
1355 const Bool_t subtracted = k >= comp.n_keep;
1356 if (subtracted)
1357 g->SetLineColor(kGray + 1);
1358 g->SetLineStyle(subtracted ? 3 : 2);
1359 g->Draw("L SAME");
1360 pads.entries.push_back(std::make_pair(g, comp.label[k]));
1361 }
1362 if (published)
1363 published->Draw("P SAME");
1364 measured->Draw("P SAME");
1365 StampPreliminary(pads.top);
1366}
1367
1368void CrossSection::DrawPullPanel(const FitFrame &frame,
1369 TGraphAsymmErrors *deviation,
1370 FitPanels &pads) const {
1371 const CrossSectionConfig &X = Constants::cfg.CROSS_SECTION_CONFIG;
1372 const Double_t x_lo = frame.fx_lo - 0.4, x_hi = frame.fx_hi + 0.4;
1373 pads.bottom->cd();
1374 const Double_t d_lim = TMath::Max(5.0, 2.0 * std::ceil(0.6 * frame.pull_max));
1375 TH1F *dframe = pads.bottom->DrawFrame(x_lo, -d_lim, x_hi, d_lim);
1376 dframe->SetTitle(Form(";%s [MeV];#delta/#sigma",
1377 X.EFFECTIVE_ENERGY ? "E_{c.m.,eff}" : "E_{c.m.}"));
1378 dframe->GetYaxis()->SetNdivisions(-202);
1379 dframe->GetYaxis()->CenterTitle(kTRUE);
1380 TLine *zero_line = new TLine(x_lo, 0.0, x_hi, 0.0);
1381 zero_line->SetLineStyle(2);
1382 zero_line->SetLineColor(kBlack);
1383 zero_line->SetLineWidth(PlottingUtils::GetLineWidth());
1384 zero_line->Draw("SAME");
1385 TLine *plus3_line = new TLine(x_lo, 3.0, x_hi, 3.0);
1386 plus3_line->SetLineStyle(3);
1387 plus3_line->SetLineColor(kGray + 2);
1388 plus3_line->SetLineWidth(PlottingUtils::GetLineWidth());
1389 plus3_line->Draw("SAME");
1390 TLine *minus3_line = new TLine(x_lo, -3.0, x_hi, -3.0);
1391 minus3_line->SetLineStyle(3);
1392 minus3_line->SetLineColor(kGray + 2);
1393 minus3_line->SetLineWidth(PlottingUtils::GetLineWidth());
1394 minus3_line->Draw("SAME");
1395 deviation->Draw("P SAME");
1396}
1397
1404void CrossSection::DrawFit(const ChannelResult &r, const TString &name) const {
1405 const CrossSectionChannel &ch = *r.ch;
1406 const Int_t m = ch.fit_model;
1407 if (m < 0 || m >= Int_t(talys_raw_.size())) {
1408 std::cerr << "cross-section: channel " << ch.name << ": fit_model " << m
1409 << " but " << talys_raw_.size() << " TALYS model(s) in the file"
1410 << std::endl;
1411 return;
1412 }
1413 FitComponents comp;
1414 if (!CollectFitComponents(r, m, comp))
1415 return;
1416 const Int_t np = Int_t(r.points.size());
1417
1418 ScaleFit fs;
1419 FitExitScales(r, comp, fs);
1420 PrintScaleFit(ch, m, comp, fs, np);
1421 PrintSubtractionCheck(r, comp, fs);
1422 std::cout << " strip E_cm,eff this work [mb] scaled model [mb] "
1423 "pull"
1424 << std::endl;
1425
1426 TGraph *sum = nullptr;
1427 TGraph *fit = nullptr;
1428 TGraphErrors *band = nullptr;
1429 if (!BuildScaledCurves(r, comp, fs, sum, fit, band))
1430 return;
1431
1432 FitFrame frame;
1433 TGraphAsymmErrors *measured = nullptr;
1434 TGraphAsymmErrors *deviation = nullptr;
1435 BuildMeasuredGraphs(r, fit, measured, deviation, frame);
1436
1437 // The published points extend the frame before x_lo/x_hi are formed.
1438 TGraphAsymmErrors *published = BuildPublishedGraph(ch, frame);
1439
1440 TString scale_text;
1441 Double_t legend_width_px = 0.0;
1442 FitLegendWidthPx(fs, ch, comp, talys_labels_[m], published != nullptr,
1443 scale_text, legend_width_px);
1444
1445 FitPanels pads;
1446 DrawFitTopPanel(ch, frame, scale_text, legend_width_px, sum, fit, band, comp,
1447 published, measured, pads);
1448 DrawPullPanel(frame, deviation, pads);
1449
1450 pads.side->cd();
1451 const Int_t n_entries = 2 + (published ? 1 : 0) + Int_t(pads.entries.size());
1452 const Double_t side_width_px =
1453 pads.plot_right_margin_px + pads.extra_width_px;
1454 const Double_t side_gap_px = 0.5 * (side_width_px - legend_width_px);
1455 TLegend *leg = PlottingUtils::AddLegend(
1456 side_gap_px / side_width_px,
1457 (side_gap_px + legend_width_px) / side_width_px, 0.5 - 0.035 * n_entries,
1458 0.5 + 0.035 * n_entries);
1459 leg->SetHeader(talys_labels_[m]);
1460 leg->AddEntry(measured, "Present Work", "pe");
1461 if (published)
1462 leg->AddEntry(published, ch.reference_label, "pe");
1463 for (Int_t k = 0; k < Int_t(pads.entries.size()); k++)
1464 leg->AddEntry(pads.entries[k].first, pads.entries[k].second,
1465 pads.entries[k].first ? "l" : "");
1466
1467 PlottingUtils::ScaleFigure(pads.plot);
1468 PlottingUtils::DrawTitle(
1469 pads.top, Form("%s%s", Paths::DatasetName().Data(), Label(ch).Data()),
1470 PlottingUtils::FigureScale(pads.plot));
1471 PlottingUtils::SaveFigure(pads.c, name, "cross_section",
1472 PlotSaveOptions::kLOG);
1473 delete pads.c;
1474}
1475
1476Bool_t CrossSection::RunChannel(const CrossSectionChannel &ch,
1477 ChannelResult &out) {
1478 const CrossSectionConfig &X = Constants::cfg.CROSS_SECTION_CONFIG;
1479 out.ch = &ch;
1480 for (Int_t k = 0; k < Int_t(ch.talys_exits.size()); k++) {
1481 Int_t z = 0, a = 0;
1482 if (!ExitResidue(ch.talys_exits[k], X.BEAM_Z, X.BEAM_A, z, a)) {
1483 std::cerr << "cross-section: channel " << ch.name << ": exit \""
1484 << ch.talys_exits[k]
1485 << "\" does not parse (light particles n p d t h a g with an "
1486 "optional multiplicity digit)"
1487 << std::endl;
1488 return kFALSE;
1489 }
1490 }
1491 for (Int_t k = 0; k < Int_t(ch.subtract_exits.size()); k++) {
1492 Int_t z = 0, a = 0;
1493 if (!ExitResidue(ch.subtract_exits[k], X.BEAM_Z, X.BEAM_A, z, a)) {
1494 std::cerr << "cross-section: channel " << ch.name
1495 << ": subtracted exit \"" << ch.subtract_exits[k]
1496 << "\" does not parse (light particles n p d t h a g with an "
1497 "optional multiplicity digit)"
1498 << std::endl;
1499 return kFALSE;
1500 }
1501 }
1502 if (!talys_raw_.empty() && ch.talys_exits.empty()) {
1503 std::cerr << "cross-section: channel " << ch.name
1504 << ": TALYS models are declared but the channel names no exits"
1505 << std::endl;
1506 return kFALSE;
1507 }
1508 // Subtraction requires the TALYS curves.
1509 if (!ch.subtract_exits.empty() && talys_raw_.empty()) {
1510 std::cerr << "cross-section: channel " << ch.name
1511 << ": subtract_exits needs the TALYS curves it subtracts; run "
1512 "talys-xs first"
1513 << std::endl;
1514 return kFALSE;
1515 }
1516 out.talys = ExitCurves(ch, ch.talys_exits, "the channel:");
1517 out.talys_sub = ExitCurves(ch, ch.subtract_exits, "to subtract:");
1518 if (!ch.subtract_exits.empty() && out.talys_sub.empty()) {
1519 std::cerr << "cross-section: channel " << ch.name
1520 << ": no model has any of the exits to subtract" << std::endl;
1521 return kFALSE;
1522 }
1523 std::cout << std::endl
1524 << " " << ch.name << " " << Label(ch) << ", region_" << ch.name
1525 << (out.talys.empty() ? " (no TALYS curve: midpoint energies)" : "")
1526 << std::endl;
1527 if (!out.talys_sub.empty())
1528 std::cout << " " << SubtractedLabel(ch)
1529 << " is not separable from it here, so TALYS's predicted share "
1530 "of each strip's count is removed; the models' spread on "
1531 "that share is a systematic"
1532 << std::endl;
1533 std::cout << " strip E_cm range [MeV] "
1534 << (X.EFFECTIVE_ENERGY ? "E_cm,eff" : "E_cm,mid")
1535 << " N_reac N_beam sigma [mb]" << std::endl;
1536 for (Int_t reac = X.XS_STRIP_MIN; reac <= X.XS_STRIP_MAX; reac++) {
1537 Point pt;
1538 if (Strip(ch, out.talys, out.talys_sub, reac, pt))
1539 out.points.push_back(pt);
1540 }
1541 if (out.points.empty()) {
1542 std::cerr << "cross-section: channel " << ch.name
1543 << ": no strip produced a cross section" << std::endl;
1544 return kFALSE;
1545 }
1546 CompareReference(out);
1547 std::vector<const ChannelResult *> one(1, &out);
1548 Draw(one, "cross_section_" + ch.name + TagSuffix());
1549 if (ch.fit_model >= 0)
1550 DrawFit(out, "cross_section_" + ch.name + "_fit" + TagSuffix());
1551 return kTRUE;
1552}
1553
1555 const CrossSectionConfig &X = Constants::cfg.CROSS_SECTION_CONFIG;
1556 const StripSumScatterConfig &C = Constants::cfg.STRIP_SUM_SCATTER_CONFIG;
1557 if (!(X.GAS_PRESSURE_TORR > 0.0) || X.BEAM_A <= 0) {
1558 std::cerr << "cross-section: this dataset has no CROSS_SECTION_CONFIG "
1559 "(gas pressure and beam are both required)"
1560 << std::endl;
1561 return kFALSE;
1562 }
1563 if (X.CHANNELS.empty()) {
1564 std::cerr << "cross-section: this dataset declares no "
1565 "CROSS_SECTION_CONFIG.CHANNELS"
1566 << std::endl;
1567 return kFALSE;
1568 }
1569 if (!LoadCache() || !LoadBeam())
1570 return kFALSE;
1571
1572 std::cout << "cross-section: " << n_beam_
1573 << " beam events past every pre-tag "
1574 << "cut, of " << n_seen_ << " seen" << std::endl;
1575 std::cout << Form(" gas %.0f Torr at %.0f K -> %.4e atoms/cm^3; strip "
1576 "%.3f cm -> %.4e atoms/cm^2",
1577 X.GAS_PRESSURE_TORR, kGasTemperatureK, n_gas_,
1578 kStripLengthCm, areal_)
1579 << std::endl;
1581 std::cout << Form(" every tagged event counted; the systematic is the cut "
1582 "variation with the gas pressure (+- %.1f Torr)",
1584 << std::endl;
1585 else
1586 std::cout << Form(" regions at %.1f sigma enclose %.1f%% of the fitted "
1587 "component and counts are corrected for that only",
1588 C.AN_REGION_NSIGMA, 100.0 * Enclosed(C.AN_REGION_NSIGMA))
1589 << std::endl;
1590 LoadTalys();
1591 if (talys_raw_.empty())
1592 std::cout << "cross-section: no TALYS graphs in root_files (run talys-xs); "
1593 "midpoint energies, no overlay"
1594 << std::endl;
1595
1596 std::vector<ChannelResult> results(X.CHANNELS.size());
1597 std::vector<const ChannelResult *> done;
1598 for (Int_t c = 0; c < Int_t(X.CHANNELS.size()); c++)
1599 if (RunChannel(X.CHANNELS[c], results[c]))
1600 done.push_back(&results[c]);
1601 if (done.empty())
1602 return kFALSE;
1603 if (done.size() > 1)
1604 Draw(done, "cross_section" + TagSuffix());
1605 cache_->Close();
1606 return kTRUE;
1607}
Dataset configuration.
Double_t TargetGasAtomsPerMolecule(TargetGas gas)
Atoms of the reacting species per molecule of fill gas.
Definition Constants.cpp:11
Absolute cross section per reaction strip, for every declared channel.
The reaction search: strip-sum scatters, beam gating and tagging.
static TString Label(const CrossSectionChannel &ch)
Display label for a channel.
static TString SubtractedLabel(const CrossSectionChannel &ch)
Label the channel's subtracted exits, or return an empty string.
Bool_t Run()
Run every channel and write the tables and figures.
static Bool_t ExitResidue(const TString &exit, Int_t z_beam, Int_t a_beam, Int_t &z, Int_t &a)
Residual nucleus left by a named exit channel.
StripSumScatterConfig STRIP_SUM_SCATTER_CONFIG
CrossSectionConfig CROSS_SECTION_CONFIG
static TString DatasetName()
The dataset's isotope name, e.g.
Definition Paths.cpp:22
static TString ResultsDir()
Absolute path to the directory receiving generated output.
Definition Paths.cpp:24
static TString CacheName()
Filename of the scatter cache for this configuration.
Bool_t Profile(const TString &path, Double_t *dE, Double_t &e_strip0)
Read the per-strip energy loss profile from a beam simulation.
Double_t CmFraction()
Centre-of-mass energy fraction for this dataset's reaction.
TString SimPath()
Path to the dataset's beam simulation file.
const TString & ActiveFileTag()
Output-name prefix of the active epoch.
const DatasetConfig & cfg
The active dataset's configuration, flat block.
TCutG * Load(const char *name, Int_t reac)
Load a cut from its per-cut file.
Double_t LoadAssigned(const char *name, Int_t reac)
The attributed count stored with a cut.
TALYS calculation with name for plot and parameters to actually run TALYS (see CrossSectionConfig::TA...
Int_t fit_model
Index into TALYS_MODELS of the model for the second figure (cross_section_<name>_fit): that model's s...
std::vector< std::vector< Double_t > > reference_xs
Published values to compare against, if any, one row per point: the effective centre-of-mass energy [...
std::vector< TString > talys_exits
Which of the residual channels TALYS wrote make up this channel's curve, as exit channels by name: wh...
TString label
The reaction as it appears in the plot title after the dataset name, e.g.
std::vector< TString > subtract_exits
Exits that cannot be separated from this channel; their TALYS branching fraction is removed from the ...
TString name
Names the region cut (region_<name>) and the output figure.
Experimental details needed to properly calculate the cross section.
Int_t FELDMAN_COUSINS_MAX_COUNT
Statistical error on a strip's count.
Int_t XS_STRIP_MIN
Reaction strips for which to calculate a cross-section.
Double_t GAS_PRESSURE_TORR_ERR
Its uncertainty [Torr]; a relative systematic on every point (the cross section scales as 1 / pressur...
Double_t GAS_PRESSURE_TORR
At the pressure the gas was actually at rather than the nominal one.
Int_t BEAM_A
Beam mass number, for the lab-to-centre-of-mass conversion; its Z and element symbol name it to a rea...
Bool_t EFFECTIVE_ENERGY
Report each strip at its effective centre-of-mass energy (the energy below which half the strip's yie...
std::vector< TalysModel > TALYS_MODELS
Hauser-Feshbach predictions from TALYS.
std::vector< CrossSectionChannel > CHANNELS
The reaction channels measured on this dataset.
AnRegionMode AN_REGION_MODE
Double_t AN_REGION_NSIGMA
compute-regions: the (a,n) region is the reaction component's AN_REGION_NSIGMA Mahalanobis ellipse fr...