MUSIC unknown
Analysis for the MUSIC active-target ionization chamber
Loading...
Searching...
No Matches
RegionCuts.cpp
Go to the documentation of this file.
1#include "RegionCuts.hpp"
2#include "Paths.hpp"
3#include "PlottingUtils.hpp"
4#include <TAxis.h>
5#include <TCanvas.h>
6#include <TFile.h>
7#include <TH1D.h>
8#include <TMath.h>
9#include <TParameter.h>
10#include <TSystem.h>
11#include <cmath>
12#include <iostream>
13#include <vector>
14
15// ---------------------------------------------------------------------------
16// Store
17// ---------------------------------------------------------------------------
18
19namespace RegionCutStore {
20
21static TString Key(const char *name, Int_t reac) {
22 return Form("%s_reac%d", name, reac);
23}
24
25TString Dir() { return Paths::ResultsDir() + "/root_files/region_cuts"; }
26
27TString Path(const char *name, Int_t reac) {
28 return Dir() + "/" + Key(name, reac) + ".root";
29}
30
31static TString LegacyPath() {
32 return Paths::ResultsDir() + "/root_files/RegionCuts.root";
33}
34
35static void WriteOne(const char *name, Int_t reac, TCutG *cut,
36 Double_t n_assigned) {
37 if (!cut)
38 return;
39 TString path = Path(name, reac);
40 TFile f(path, "RECREATE");
41 if (f.IsZombie()) {
42 std::cerr << " [region] cannot open " << path << " to save cut"
43 << std::endl;
44 return;
45 }
46 f.cd();
47 cut->Write(name);
48 if (n_assigned >= 0.0)
49 TParameter<Double_t>("n_assigned", n_assigned).Write();
50 f.Close();
51 std::cout << " [region] saved " << name << " reac " << reac << " -> " << path
52 << std::endl;
53}
54
55void Save(Int_t reac, TCutG *cut_an, TCutG *cut_aa, Double_t n_an_assigned) {
56 gSystem->mkdir(Dir(), kTRUE);
57 WriteOne("region_an", reac, cut_an, n_an_assigned);
58 WriteOne("region_aa", reac, cut_aa, -1.0);
59}
60
61static const char *kFitKeys[14] = {
62 "fit_beam_amp", "fit_beam_mx", "fit_beam_sx", "fit_beam_my", "fit_beam_sy",
63 "fit_beam_rho", "fit_reac_amp", "fit_reac_mx", "fit_reac_sx", "fit_reac_my",
64 "fit_reac_sy", "fit_reac_rho", "fit_n_beam", "fit_n_reac"};
65
66static void FitValues(const RegionFit &fit, Double_t *v) {
67 const Gauss2D *g[2] = {&fit.beam, &fit.reac};
68 for (Int_t k = 0; k < 2; k++) {
69 v[6 * k + 0] = g[k]->amp;
70 v[6 * k + 1] = g[k]->mx;
71 v[6 * k + 2] = g[k]->sx;
72 v[6 * k + 3] = g[k]->my;
73 v[6 * k + 4] = g[k]->sy;
74 v[6 * k + 5] = g[k]->rho;
75 }
76 v[12] = fit.n_beam;
77 v[13] = fit.n_reac;
78}
79
80void SaveFit(Int_t reac, const RegionFit &fit) {
81 TString path = Path("region_an", reac);
82 TFile f(path, "UPDATE");
83 if (f.IsZombie()) {
84 std::cerr << " [region] cannot open " << path << " to save the fit"
85 << std::endl;
86 return;
87 }
88 Double_t v[14];
89 FitValues(fit, v);
90 f.cd();
91 for (Int_t k = 0; k < 14; k++)
92 TParameter<Double_t>(kFitKeys[k], v[k])
93 .Write(kFitKeys[k], TObject::kOverwrite);
94 f.Close();
95}
96
97Bool_t LoadFit(Int_t reac, RegionFit &fit) {
98 TString path = Path("region_an", reac);
99 if (gSystem->AccessPathName(path))
100 return kFALSE;
101 TFile f(path, "READ");
102 if (f.IsZombie())
103 return kFALSE;
104 Double_t v[14];
105 for (Int_t k = 0; k < 14; k++) {
106 TParameter<Double_t> *p =
107 dynamic_cast<TParameter<Double_t> *>(f.Get(kFitKeys[k]));
108 if (!p)
109 return kFALSE;
110 v[k] = p->GetVal();
111 }
112 Gauss2D *g[2] = {&fit.beam, &fit.reac};
113 for (Int_t k = 0; k < 2; k++) {
114 g[k]->amp = v[6 * k + 0];
115 g[k]->mx = v[6 * k + 1];
116 g[k]->sx = v[6 * k + 2];
117 g[k]->my = v[6 * k + 3];
118 g[k]->sy = v[6 * k + 4];
119 g[k]->rho = v[6 * k + 5];
120 }
121 fit.n_beam = v[12];
122 fit.n_reac = v[13];
123 fit.ok = kTRUE;
124 return kTRUE;
125}
126
127Double_t LoadAssigned(const char *name, Int_t reac) {
128 TString path = Path(name, reac);
129 if (gSystem->AccessPathName(path))
130 return -1.0;
131 TFile f(path, "READ");
132 if (f.IsZombie())
133 return -1.0;
134 TParameter<Double_t> *p =
135 dynamic_cast<TParameter<Double_t> *>(f.Get("n_assigned"));
136 const Double_t n = p ? p->GetVal() : -1.0;
137 f.Close();
138 return n;
139}
140
141static TCutG *ReadFrom(const TString &path, const char *key, const char *name) {
142 if (gSystem->AccessPathName(path))
143 return nullptr;
144 TFile f(path, "READ");
145 if (f.IsZombie())
146 return nullptr;
147 TCutG *stored = dynamic_cast<TCutG *>(f.Get(key));
148 if (!stored) {
149 f.Close();
150 return nullptr;
151 }
152 // The file owns the object; hand back a copy that outlives the close.
153 TCutG *cut = static_cast<TCutG *>(stored->Clone(name));
154 f.Close();
155 return cut;
156}
157
158TCutG *Load(const char *name, Int_t reac) {
159 TCutG *cut = ReadFrom(Path(name, reac), name, name);
160 if (cut)
161 return cut;
162 return ReadFrom(LegacyPath(), Key(name, reac), name);
163}
164
165// Hand-drawn only. A per-cut file written by the interactive draw carries no
166// n_assigned; one written by compute-regions does and is skipped. Then the
167// pre-split RegionCuts.root, which only ever held drawn cuts.
168TCutG *LoadDrawn(const char *name, Int_t reac) {
169 TString path = Path(name, reac);
170 if (!gSystem->AccessPathName(path)) {
171 TFile f(path, "READ");
172 const Bool_t fitted = !f.IsZombie() && f.Get("n_assigned") != nullptr;
173 f.Close();
174 if (!fitted)
175 if (TCutG *cut = ReadFrom(path, name, name))
176 return cut;
177 }
178 return ReadFrom(LegacyPath(), Key(name, reac), name);
179}
180
181} // namespace RegionCutStore
182
183// ---------------------------------------------------------------------------
184// Finder
185// ---------------------------------------------------------------------------
186
187namespace {
188
189// ROOT's "bigaus": peak height `amp` in counts per bin, no normalisation.
190Double_t Bigaus(const Gauss2D &g, Double_t x, Double_t y) {
191 const Double_t dx = (x - g.mx) / g.sx, dy = (y - g.my) / g.sy;
192 const Double_t r2 = 1.0 - g.rho * g.rho;
193 return g.amp *
194 std::exp(-0.5 / r2 * (dx * dx + dy * dy - 2.0 * g.rho * dx * dy));
195}
196
197// Squared Mahalanobis distance of (x, y) from a component.
198Double_t Mahal2(const Gauss2D &g, Double_t x, Double_t y) {
199 const Double_t dx = (x - g.mx) / g.sx, dy = (y - g.my) / g.sy;
200 return (dx * dx + dy * dy - 2.0 * g.rho * dx * dy) / (1.0 - g.rho * g.rho);
201}
202
203// log of the bivariate normalisation, so densities of two components with
204// different widths compare fairly.
205Double_t LogNorm(const Gauss2D &g) {
206 return std::log(2.0 * TMath::Pi() * g.sx * g.sy *
207 std::sqrt(1.0 - g.rho * g.rho));
208}
209
210// A component from weighted moments (sum w, sum wx, sum wy, sum wxx, sum wyy,
211// sum wxy). Widths are floored at a bin, the correlation clamped, and the
212// amplitude is the peak height in counts per bin of a Gaussian holding sw
213// events, which is what ModelHist needs to draw it on the data's scale.
214Gauss2D MomentsToGauss(Double_t sw, Double_t sx, Double_t sy, Double_t sxx,
215 Double_t syy, Double_t sxy, Double_t bwx, Double_t bwy) {
216 Gauss2D g;
217 g.mx = sx / sw;
218 g.my = sy / sw;
219 const Double_t vx = sxx / sw - g.mx * g.mx, vy = syy / sw - g.my * g.my;
220 const Double_t cv = sxy / sw - g.mx * g.my;
221 g.sx = std::sqrt(TMath::Max(vx, bwx * bwx));
222 g.sy = std::sqrt(TMath::Max(vy, bwy * bwy));
223 g.rho = TMath::Max(-0.95, TMath::Min(0.95, cv / (g.sx * g.sy)));
224 g.amp = sw * bwx * bwy /
225 (2.0 * TMath::Pi() * g.sx * g.sy * std::sqrt(1.0 - g.rho * g.rho));
226 return g;
227}
228
229// FWHM-based width of a projection around its maximum (over its set range).
230Double_t WidthAtMax(TH1D *h, Double_t &at) {
231 Int_t b = h->GetMaximumBin();
232 Double_t c = h->GetBinContent(b);
233 at = h->GetBinCenter(b);
234 Int_t lo = b, hi = b;
235 while (lo > 1 && h->GetBinContent(lo) > 0.5 * c)
236 lo--;
237 while (hi < h->GetNbinsX() && h->GetBinContent(hi) > 0.5 * c)
238 hi++;
239 return TMath::Max(h->GetBinWidth(1),
240 (h->GetBinCenter(hi) - h->GetBinCenter(lo)) / 2.355);
241}
242
243// Height above the ridge: RegionCutFinder::AboveRidge, declared in the header
244// so cross-section can rebuild a band. The reaction island lives at u ~ 4;
245// the ridge itself, whatever its x, is at u ~ 0. This is the separation a
246// plain y projection washes out.
248
249// The fitted mixture evaluated per bin over the window, in a copy of the
250// scatter, so it can be projected and contoured exactly like the data.
251TH2F *ModelHist(TH2F *scatter, const RegionFit &fit, Int_t reac) {
252 TH2F *m = static_cast<TH2F *>(scatter->Clone(Form("model_reac%d", reac)));
253 m->SetDirectory(nullptr);
254 m->Reset();
255 TAxis *ax = m->GetXaxis(), *ay = m->GetYaxis();
256 for (Int_t i = ax->FindBin(fit.x_lo); i <= ax->FindBin(fit.x_hi); i++)
257 for (Int_t j = ay->FindBin(fit.y_lo); j <= ay->FindBin(fit.y_hi); j++) {
258 Double_t x = ax->GetBinCenter(i), y = ay->GetBinCenter(j);
259 m->SetBinContent(i, j,
260 Bigaus(fit.beam, x, y) +
261 (fit.has_reac ? Bigaus(fit.reac, x, y) : 0.0));
262 }
263 return m;
264}
265
266// The beam-like component from its core: the maximum and FWHM of each
267// projection seed a +-2 sigma box, and the box's moments give the component.
268// No minimiser: on a peak of 1e5 counts per bin Minuit's bigaus fit runs the
269// amplitude to its bound and walks the mean out of the window, while the
270// moments are exact. Returns kFALSE (with why) on an empty core.
271Bool_t BeamFromCore(TH2F *scatter, Int_t reac, Int_t bx0, Int_t bx1, Int_t by0,
272 Int_t by1, Gauss2D &beam, TString &why) {
273 TAxis *ax = scatter->GetXaxis(), *ay = scatter->GetYaxis();
274 const Double_t bwx = ax->GetBinWidth(1), bwy = ay->GetBinWidth(1);
275 TH1D *px = scatter->ProjectionX(Form("rcf_px_%d", reac), by0, by1);
276 TH1D *py = scatter->ProjectionY(Form("rcf_py_%d", reac), bx0, bx1);
277 px->SetDirectory(nullptr);
278 py->SetDirectory(nullptr);
279 px->GetXaxis()->SetRange(bx0, bx1);
280 py->GetXaxis()->SetRange(by0, by1);
281 Double_t mx0 = 0.0, my0 = 0.0;
282 const Double_t sx0 = WidthAtMax(px, mx0), sy0 = WidthAtMax(py, my0);
283 delete px;
284 delete py;
285 Double_t sw = 0, sx = 0, sy = 0, sxx = 0, syy = 0, sxy = 0;
286 for (Int_t i = ax->FindBin(mx0 - 2 * sx0); i <= ax->FindBin(mx0 + 2 * sx0);
287 i++)
288 for (Int_t j = ay->FindBin(my0 - 2 * sy0); j <= ay->FindBin(my0 + 2 * sy0);
289 j++) {
290 Double_t w = scatter->GetBinContent(i, j);
291 Double_t x = ax->GetBinCenter(i), y = ay->GetBinCenter(j);
292 sw += w;
293 sx += w * x;
294 sy += w * y;
295 sxx += w * x * x;
296 syy += w * y * y;
297 sxy += w * x * y;
298 }
299 if (!(sw > 0)) {
300 why = "empty beam core";
301 return kFALSE;
302 }
303 beam = MomentsToGauss(sw, sx, sy, sxx, syy, sxy, bwx, bwy);
304 return kTRUE;
305}
306
307} // namespace
308
309namespace RegionCutFinder {
310
311RegionFit FitMixture(TH2F *scatter, Int_t reac, Double_t x_lo, Double_t x_hi,
312 Double_t y_lo, Double_t y_hi) {
313 RegionFit fit;
314 fit.x_lo = x_lo;
315 fit.x_hi = x_hi;
316 fit.y_lo = y_lo;
317 fit.y_hi = y_hi;
318 TAxis *ax = scatter->GetXaxis(), *ay = scatter->GetYaxis();
319 const Int_t bx0 = ax->FindBin(x_lo), bx1 = ax->FindBin(x_hi);
320 const Int_t by0 = ay->FindBin(y_lo), by1 = ay->FindBin(y_hi);
321 const Double_t bwx = ax->GetBinWidth(1), bwy = ay->GetBinWidth(1);
322
323 Gauss2D beam;
324 if (!BeamFromCore(scatter, reac, bx0, bx1, by0, by1, beam, fit.why))
325 return fit;
326
327 // Reaction seed. The island is both above the ridge (u ~ 4) and beyond the
328 // beam in x, while the beam's own tail above the ridge spreads over the
329 // ridge's x range. So: in the band 3..15 conditional sigma above the ridge,
330 // take the x maximum beyond +3 sigma_x of the beam, and seed at the centroid
331 // of that neighbourhood. A local maximum in any 1D projection is not
332 // required -- the real beam tail is heavier than Gaussian and the island
333 // usually sits on a slope, not a bump.
334 TH1D hx(Form("rcf_hx_%d", reac), "", 80, x_lo, x_hi);
335 hx.SetDirectory(nullptr);
336 for (Int_t i = bx0; i <= bx1; i++)
337 for (Int_t j = by0; j <= by1; j++) {
338 const Double_t w = scatter->GetBinContent(i, j);
339 if (!(w > 0))
340 continue;
341 const Double_t x = ax->GetBinCenter(i), y = ay->GetBinCenter(j);
342 const Double_t u = AboveRidge(beam, x, y);
343 if (u > 3.0 && u < 15.0 && x > beam.mx + 3.0 * beam.sx)
344 hx.Fill(x, w);
345 }
346 const Int_t bpk = hx.GetMaximumBin();
347 const Double_t xpk = hx.GetBinCenter(bpk);
348 if (hx.GetBinContent(bpk) < 5.0) {
349 fit.why = "nothing above the ridge beyond +3 sigma_x of the beam";
350 return fit;
351 }
352 Double_t s[6] = {0, 0, 0, 0, 0, 0};
353 for (Int_t i = bx0; i <= bx1; i++)
354 for (Int_t j = by0; j <= by1; j++) {
355 const Double_t w = scatter->GetBinContent(i, j);
356 if (!(w > 0))
357 continue;
358 const Double_t x = ax->GetBinCenter(i), y = ay->GetBinCenter(j);
359 const Double_t u = AboveRidge(beam, x, y);
360 if (u > 3.0 && u < 15.0 && std::fabs(x - xpk) < 2.0 * beam.sx) {
361 s[0] += w;
362 s[1] += w * x;
363 s[2] += w * y;
364 s[3] += w * x * x;
365 s[4] += w * y * y;
366 s[5] += w * x * y;
367 }
368 }
369 if (s[0] < 20.0) {
370 fit.why = Form("only %.0f events in the seed neighbourhood", s[0]);
371 return fit;
372 }
373 Gauss2D reac_g = beam; // shape starts as the beam's
374 reac_g.mx = s[1] / s[0];
375 reac_g.my = s[2] / s[0];
376
377 // Classification EM over the window, trimmed and capped: a bin joins the
378 // reaction component only if it is above the ridge (u > 2), within 3 sigma
379 // of the component, and the component's log-density with its prior beats
380 // the beam's. Each component is then re-estimated from the moments of its
381 // bins, the reaction's widths capped at twice the beam's. Without the trim
382 // and cap the reaction component swallows the beam's non-Gaussian halo and
383 // ends up as a wide blob on the ridge; the prior is what keeps a bin four
384 // sigma down the beam tail with the beam.
385 Double_t n_beam = 0.0, n_reac = 0.0;
386 Double_t total = 0.0;
387 for (Int_t i = bx0; i <= bx1; i++)
388 for (Int_t j = by0; j <= by1; j++)
389 total += scatter->GetBinContent(i, j);
390 Double_t prior_reac = 1.0e-3; // until the first assignment counts it
391 for (Int_t iter = 0; iter < 20; iter++) {
392 Double_t mb[6] = {0, 0, 0, 0, 0, 0}, mr[6] = {0, 0, 0, 0, 0, 0};
393 const Double_t lp_b = std::log(1.0 - prior_reac) - LogNorm(beam);
394 const Double_t lp_r = std::log(prior_reac) - LogNorm(reac_g);
395 for (Int_t i = bx0; i <= bx1; i++)
396 for (Int_t j = by0; j <= by1; j++) {
397 const Double_t w = scatter->GetBinContent(i, j);
398 if (!(w > 0))
399 continue;
400 const Double_t x = ax->GetBinCenter(i), y = ay->GetBinCenter(j);
401 const Double_t d2r = Mahal2(reac_g, x, y);
402 // Admission starts where the seed search did, 3 sigma above the
403 // ridge: letting bins in from 2 sigma handed the component the
404 // beam's upper tail sheet, which it then tilted along the ridge to
405 // absorb, and at strips where the island is only ~3 sigma_x off the
406 // ridge that doubled the count with background.
407 const Bool_t to_reac =
408 AboveRidge(beam, x, y) > 3.0 && d2r < 9.0 &&
409 (lp_r - 0.5 * d2r) > (lp_b - 0.5 * Mahal2(beam, x, y));
410 Double_t *m = to_reac ? mr : mb;
411 m[0] += w;
412 m[1] += w * x;
413 m[2] += w * y;
414 m[3] += w * x * x;
415 m[4] += w * y * y;
416 m[5] += w * x * y;
417 }
418 if (mr[0] < 20.0) {
419 fit.why = Form("reaction component starved (%.0f events) at iteration %d",
420 mr[0], iter);
421 return fit;
422 }
423 Gauss2D nb =
424 MomentsToGauss(mb[0], mb[1], mb[2], mb[3], mb[4], mb[5], bwx, bwy);
425 Gauss2D nr =
426 MomentsToGauss(mr[0], mr[1], mr[2], mr[3], mr[4], mr[5], bwx, bwy);
427 // The island is the beam blob displaced by the reaction. In the
428 // simulation, which has no background to confuse the measurement, it is
429 // 4-6% wider than the beam in x and 8-10% in y at every strip: the
430 // vertex position within the strip and the kinematics add that little.
431 // So the cap is 1.1x. Anything looser lets the component take in beam
432 // tail at strips where the island sits only ~3 sigma_x off the ridge
433 // (at 1.5x, strip 5 held 931 events against 781 at the beam's width).
434 nr.sx = TMath::Min(nr.sx, 1.1 * nb.sx);
435 nr.sy = TMath::Min(nr.sy, 1.1 * nb.sy);
436 // The island's x-y correlation comes from the same beam-energy spread as
437 // the beam blob's, so it cannot exceed it: a reaction component more
438 // elongated along the ridge than the beam is tracing the beam's tail.
439 if (std::fabs(nr.rho) > std::fabs(nb.rho))
440 nr.rho = nr.rho < 0 ? -std::fabs(nb.rho) : std::fabs(nb.rho);
441 const Bool_t moved = std::fabs(nr.mx - reac_g.mx) > 0.1 * bwx ||
442 std::fabs(nr.my - reac_g.my) > 0.1 * bwy;
443 beam = nb;
444 reac_g = nr;
445 n_beam = mb[0];
446 n_reac = mr[0];
447 prior_reac = TMath::Max(1.0e-6, n_reac / total);
448 if (!moved)
449 break;
450 }
451 fit.beam = beam;
452 fit.reac = reac_g;
453
454 if (fit.reac.my < fit.beam.my + 2.0 * fit.beam.sy) {
455 fit.why = "reaction component fell back onto the ridge";
456 return fit;
457 }
458 if (n_reac < 20.0) {
459 fit.why = Form("reaction component holds only %.0f events", n_reac);
460 return fit;
461 }
462 fit.n_beam = n_beam;
463 fit.n_reac = n_reac;
464 fit.ok = kTRUE;
465 return fit;
466}
467
468Double_t AboveRidge(const Gauss2D &b, Double_t x, Double_t y) {
469 return ((y - b.my) - b.rho * (b.sy / b.sx) * (x - b.mx)) /
470 (b.sy * std::sqrt(1.0 - b.rho * b.rho));
471}
472
473RegionFit FitBeam(TH2F *scatter, Int_t reac, Double_t x_lo, Double_t x_hi,
474 Double_t y_lo, Double_t y_hi) {
475 RegionFit fit;
476 fit.x_lo = x_lo;
477 fit.x_hi = x_hi;
478 fit.y_lo = y_lo;
479 fit.y_hi = y_hi;
480 fit.has_reac = kFALSE;
481 TAxis *ax = scatter->GetXaxis(), *ay = scatter->GetYaxis();
482 if (!BeamFromCore(scatter, reac, ax->FindBin(x_lo), ax->FindBin(x_hi),
483 ay->FindBin(y_lo), ay->FindBin(y_hi), fit.beam, fit.why))
484 return fit;
485 fit.reac = fit.beam;
486 fit.reac.amp = 0.0;
487 fit.ok = kTRUE;
488 return fit;
489}
490
491TCutG *RidgeBandCut(const char *name, const Gauss2D &beam, Double_t nsig_lo,
492 Double_t nsig_hi, Double_t x_lo, Double_t x_hi,
493 Double_t y_lo, Double_t y_hi) {
494 // y on the ridge line at x, plus n conditional sigma; clipped to the window.
495 auto edge = [&](Double_t x, Double_t n) {
496 const Double_t y = beam.my +
497 beam.rho * (beam.sy / beam.sx) * (x - beam.mx) +
498 n * beam.sy * std::sqrt(1.0 - beam.rho * beam.rho);
499 return TMath::Max(y_lo, TMath::Min(y_hi, y));
500 };
501 TCutG *c = new TCutG(name, 5);
502 c->SetPoint(0, x_lo, edge(x_lo, nsig_lo));
503 c->SetPoint(1, x_hi, edge(x_hi, nsig_lo));
504 c->SetPoint(2, x_hi, edge(x_hi, nsig_hi));
505 c->SetPoint(3, x_lo, edge(x_lo, nsig_hi));
506 c->SetPoint(4, x_lo, edge(x_lo, nsig_lo));
507 c->SetLineColor(kBlack);
508 c->SetLineWidth(2);
509 return c;
510}
511
512TCutG *EllipseCut(const char *name, const Gauss2D &g, Double_t nsigma,
513 Int_t npts) {
514 // Mahalanobis contour at nsigma, via the Cholesky factor of the covariance.
515 TCutG *c = new TCutG(name, npts + 1);
516 const Double_t q = std::sqrt(1.0 - g.rho * g.rho);
517 for (Int_t i = 0; i <= npts; i++) {
518 Double_t t = 2.0 * TMath::Pi() * i / npts;
519 Double_t u = std::cos(t), v = std::sin(t);
520 c->SetPoint(i, g.mx + nsigma * g.sx * u,
521 g.my + nsigma * g.sy * (g.rho * u + q * v));
522 }
523 c->SetLineColor(kBlack);
524 c->SetLineWidth(2);
525 return c;
526}
527
528Double_t CountInside(TH2F *scatter, TCutG *cut, const RegionFit &fit) {
529 if (!cut)
530 return 0.0;
531 TAxis *ax = scatter->GetXaxis(), *ay = scatter->GetYaxis();
532 Double_t n = 0.0;
533 for (Int_t i = ax->FindBin(fit.x_lo); i <= ax->FindBin(fit.x_hi); i++)
534 for (Int_t j = ay->FindBin(fit.y_lo); j <= ay->FindBin(fit.y_hi); j++)
535 if (cut->IsInside(ax->GetBinCenter(i), ay->GetBinCenter(j)))
536 n += scatter->GetBinContent(i, j);
537 return n;
538}
539
540void SaveFigures(TH2F *scatter, Int_t reac, const RegionFit &fit, TCutG *an,
541 TCutG *aa, const TString &subdir) {
542 scatter->GetXaxis()->SetRangeUser(fit.x_lo, fit.x_hi);
543 scatter->GetYaxis()->SetRangeUser(fit.y_lo, fit.y_hi);
544 const Int_t cReac = kRed + 1, cBeam = kAzure + 1;
545
546 // 1. The regions on the scatter, with each fitted component's 1/2/3 sigma
547 // contours dashed behind them, so the fit and the cut it produced are
548 // both on the page.
549 {
550 TCanvas *c = PlottingUtils::GetConfiguredCanvas(kFALSE);
551 c->SetLogz(kTRUE);
552 PlottingUtils::ConfigureAndDraw2DHistogram(
553 scatter, c,
554 Form("reac %d: fitted components (dashed 1,2,3#sigma) and regions "
555 "(filled)",
556 reac));
557 std::vector<TCutG *> tmp;
558 for (Int_t k = 1; k <= 3; k++) {
559 TCutG *eb = EllipseCut(Form("cb%d_%d", k, reac), fit.beam, k);
560 eb->SetLineColor(cBeam);
561 eb->SetLineStyle(2);
562 eb->SetLineWidth(1);
563 eb->Draw("L SAME");
564 tmp.push_back(eb);
565 if (!fit.has_reac)
566 continue;
567 TCutG *er = EllipseCut(Form("cr%d_%d", k, reac), fit.reac, k);
568 er->SetLineColor(cReac);
569 er->SetLineStyle(2);
570 er->SetLineWidth(1);
571 er->Draw("L SAME");
572 tmp.push_back(er);
573 }
574 if (aa) {
575 aa->SetFillColorAlpha(cBeam, 0.15);
576 aa->SetLineColor(cBeam);
577 aa->Draw("F SAME");
578 aa->Draw("L SAME");
579 }
580 if (an) {
581 an->SetFillColorAlpha(cReac, 0.25);
582 an->SetLineColor(cReac);
583 an->Draw("F SAME");
584 an->Draw("L SAME");
585 }
586 // 2D: z is already log from the utility; kLOG means a log y here.
587 PlottingUtils::SaveFigure(c, Form("regions_reac%d", reac), subdir,
588 PlotSaveOptions::kLINEAR);
589 delete c;
590 for (Int_t k = 0; k < Int_t(tmp.size()); k++)
591 delete tmp[k];
592 }
593
594 // 2. The fitted density itself as contours over the data.
595 TH2F *model = ModelHist(scatter, fit, reac);
596 {
597 TCanvas *c = PlottingUtils::GetConfiguredCanvas(kFALSE);
598 c->SetLogz(kTRUE);
599 PlottingUtils::ConfigureAndDraw2DHistogram(
600 scatter, c,
601 Form("reac %d: data with the fitted mixture's density", reac));
602 model->SetContour(10);
603 model->SetLineColor(cReac);
604 model->SetLineWidth(1);
605 model->Draw("CONT3 SAME");
606 PlottingUtils::SaveFigure(c, Form("regions_reac%d_model", reac), subdir,
607 PlotSaveOptions::kLINEAR);
608 delete c;
609 }
610
611 // 3./4. Each projection with the model's projection overlaid.
612 TAxis *ax = scatter->GetXaxis(), *ay = scatter->GetYaxis();
613 const Int_t bx0 = ax->FindBin(fit.x_lo), bx1 = ax->FindBin(fit.x_hi);
614 const Int_t by0 = ay->FindBin(fit.y_lo), by1 = ay->FindBin(fit.y_hi);
615 for (Int_t which = 0; which < 2; which++) {
616 TH1D *d = which == 0 ? scatter->ProjectionX(Form("dpx_%d", reac), by0, by1)
617 : scatter->ProjectionY(Form("dpy_%d", reac), bx0, bx1);
618 TH1D *m = which == 0 ? model->ProjectionX(Form("mpx_%d", reac), by0, by1)
619 : model->ProjectionY(Form("mpy_%d", reac), bx0, bx1);
620 d->SetDirectory(nullptr);
621 m->SetDirectory(nullptr);
622 d->GetXaxis()->SetRange(which == 0 ? bx0 : by0, which == 0 ? bx1 : by1);
623 TCanvas *c = PlottingUtils::GetConfiguredCanvas(kTRUE);
624 PlottingUtils::ConfigureAndDrawHistogram(
625 d, kBlack,
626 Form("reac %d: %s projection, data (black) and fitted mixture (red)",
627 reac, which == 0 ? "x" : "y"));
628 m->SetLineColor(cReac);
629 m->SetLineWidth(2);
630 m->Draw("HIST SAME");
631 PlottingUtils::SaveFigure(
632 c, Form("regions_reac%d_proj%s", reac, which == 0 ? "x" : "y"), subdir,
633 PlotSaveOptions::kLOG);
634 delete c;
635 delete d;
636 delete m;
637 }
638 delete model;
639}
640
641} // namespace RegionCutFinder
static TString ResultsDir()
Absolute path to the directory receiving generated output.
Definition Paths.cpp:24
Finding regions by fitting a two-component mixture to the scatter.
RegionFit FitMixture(TH2F *scatter, Int_t reac, Double_t x_lo, Double_t x_hi, Double_t y_lo, Double_t y_hi)
Fit the two-component mixture to one strip's scatter.
TCutG * EllipseCut(const char *name, const Gauss2D &g, Double_t nsigma, Int_t npts=64)
Closed polygon of a component's Mahalanobis contour.
TCutG * RidgeBandCut(const char *name, const Gauss2D &beam, Double_t nsig_lo, Double_t nsig_hi, Double_t x_lo, Double_t x_hi, Double_t y_lo, Double_t y_hi)
Closed polygon of a band above the beam ridge.
Double_t AboveRidge(const Gauss2D &beam, Double_t x, Double_t y)
How far a point sits above the beam ridge, in conditional sigma.
void SaveFigures(TH2F *scatter, Int_t reac, const RegionFit &fit, TCutG *an, TCutG *aa, const TString &subdir)
Save the diagnostic figures for a strip's regions.
RegionFit FitBeam(TH2F *scatter, Int_t reac, Double_t x_lo, Double_t x_hi, Double_t y_lo, Double_t y_hi)
Fit the beam component alone, with no reaction component.
Double_t CountInside(TH2F *scatter, TCutG *cut, const RegionFit &fit)
Count scatter events inside a cut.
On-disk home of the per-strip (a,n) and (a,a') region cuts.
TCutG * LoadDrawn(const char *name, Int_t reac)
Load only a hand-drawn cut, never a fitted one.
TString Path(const char *name, Int_t reac)
Path to one cut's file.
void Save(Int_t reac, TCutG *cut_an, TCutG *cut_aa, Double_t n_an_assigned=-1.0)
Write a strip's two region cuts.
void SaveFit(Int_t reac, const RegionFit &fit)
Store the fitted components beside the (a,n) cut.
Bool_t LoadFit(Int_t reac, RegionFit &fit)
Read back a stored fit.
TString Dir()
Directory holding the region cut files.
TCutG * Load(const char *name, Int_t reac)
Load a cut, from either storage generation.
Double_t LoadAssigned(const char *name, Int_t reac)
The attributed count stored with a cut.
One bivariate Gaussian component of a strip's scatter.
Double_t mx
Mean in x.
Double_t sx
Width in x.
Double_t amp
Amplitude.
Double_t rho
x-y correlation, in [-1, 1].
Double_t sy
Width in y.
Double_t my
Mean in y.
Result of fitting the two-component mixture to one strip's scatter.
Double_t y_hi
Fit window.
Double_t n_beam
Events the mixture attributes to each component.
Double_t x_lo
Bool_t has_reac
kFALSE for a beam-only fit, as used by the ridge-band region mode.
Double_t n_reac
TString why
Why the fit failed; set only when ok is false.
Bool_t ok
Whether the fit succeeded. Check this first.
Gauss2D reac
The reaction population above the ridge.
Gauss2D beam
The beam-like population, pinned from its own core.
Double_t y_lo
Double_t x_hi