UNPKG

jsroot

Version:
252 lines (206 loc) 9.01 kB
import { BIT, create, createHistogram, isStr, clTH1, clTH2, clTH2F, kNoStats } from '../core.mjs'; import { ObjectPainter } from '../base/ObjectPainter.mjs'; import { TGraphPainter, clTGraphAsymmErrors } from '../hist2d/TGraphPainter.mjs'; import { TF1Painter } from './TF1Painter.mjs'; import { TH1Painter } from './TH1Painter.mjs'; import { TH2Painter } from './TH2Painter.mjs'; import { getTEfficiencyBoundaryFunc } from '../base/math.mjs'; const kIsBayesian = BIT(14), // Bayesian statistics are used kPosteriorMode = BIT(15), // Use posterior mean for best estimate (Bayesian statistics) // kShortestInterval = BIT(16), // Use shortest interval, not implemented - too complicated kUseBinPrior = BIT(17), // Use a different prior for each bin kUseWeights = BIT(18), // Use weights getBetaAlpha = (obj, bin) => { return (obj.fBeta_bin_params.length > bin) ? obj.fBeta_bin_params[bin].first : obj.fBeta_alpha; }, getBetaBeta = (obj, bin) => { return (obj.fBeta_bin_params.length > bin) ? obj.fBeta_bin_params[bin].second : obj.fBeta_beta; }; /** * @summary Painter for TEfficiency object * * @private */ class TEfficiencyPainter extends ObjectPainter { /** @summary Calculate efficiency */ getEfficiency(obj, bin) { const BetaMean = (a, b) => { return (a <= 0 || b <= 0) ? 0 : a / (a + b); }, BetaMode = (a, b) => { if (a <= 0 || b <= 0) return 0; if (a <= 1 || b <= 1) { if (a < b) return 0; if (a > b) return 1; if (a === b) return 0.5; // cannot do otherwise } return (a - 1.0) / (a + b - 2.0); }, total = obj.fTotalHistogram.fArray[bin], // should work for both 1-d and 2-d passed = obj.fPassedHistogram.fArray[bin]; // should work for both 1-d and 2-d if (obj.TestBit(kIsBayesian)) { // parameters for the beta prior distribution const alpha = obj.TestBit(kUseBinPrior) ? getBetaAlpha(obj, bin) : obj.fBeta_alpha, beta = obj.TestBit(kUseBinPrior) ? getBetaBeta(obj, bin) : obj.fBeta_beta; let aa, bb; if (obj.TestBit(kUseWeights)) { const tw = total, // fTotalHistogram->GetBinContent(bin); tw2 = obj.fTotalHistogram.fSumw2 ? obj.fTotalHistogram.fSumw2[bin] : Math.abs(total), pw = passed; // fPassedHistogram->GetBinContent(bin); if (tw2 <= 0) return pw / tw; // tw/tw2 re-normalize the weights const norm = tw / tw2; aa = pw * norm + alpha; bb = (tw - pw) * norm + beta; } else { aa = passed + alpha; bb = total - passed + beta; } return !obj.TestBit(kPosteriorMode) ? BetaMean(aa, bb) : BetaMode(aa, bb); } return total ? passed / total : 0; } /** @summary Calculate efficiency error low */ getEfficiencyErrorLow(obj, bin, value) { const total = obj.fTotalHistogram.fArray[bin], passed = obj.fPassedHistogram.fArray[bin]; let alpha = 0, beta = 0; if (obj.TestBit(kIsBayesian)) { alpha = obj.TestBit(kUseBinPrior) ? getBetaAlpha(obj, bin) : obj.fBeta_alpha; beta = obj.TestBit(kUseBinPrior) ? getBetaBeta(obj, bin) : obj.fBeta_beta; } return value - this.fBoundary(total, passed, obj.fConfLevel, false, alpha, beta); } /** @summary Calculate efficiency error low up */ getEfficiencyErrorUp(obj, bin, value) { const total = obj.fTotalHistogram.fArray[bin], passed = obj.fPassedHistogram.fArray[bin]; let alpha = 0, beta = 0; if (obj.TestBit(kIsBayesian)) { alpha = obj.TestBit(kUseBinPrior) ? getBetaAlpha(obj, bin) : obj.fBeta_alpha; beta = obj.TestBit(kUseBinPrior) ? getBetaBeta(obj, bin) : obj.fBeta_beta; } return this.fBoundary(total, passed, obj.fConfLevel, true, alpha, beta) - value; } /** @summary Copy drawing attributes */ copyAttributes(obj, eff) { ['fLineColor', 'fLineStyle', 'fLineWidth', 'fFillColor', 'fFillStyle', 'fMarkerColor', 'fMarkerStyle', 'fMarkerSize'].forEach(name => { obj[name] = eff[name]; }); } /** @summary Create graph for the drawing of 1-dim TEfficiency */ createGraph(/* eff */) { const gr = create(clTGraphAsymmErrors); gr.fName = 'eff_graph'; return gr; } /** @summary Create histogram for the drawing of 2-dim TEfficiency */ createHisto(eff) { const nbinsx = eff.fTotalHistogram.fXaxis.fNbins, nbinsy = eff.fTotalHistogram.fYaxis.fNbins, hist = createHistogram(clTH2F, nbinsx, nbinsy); Object.assign(hist.fXaxis, eff.fTotalHistogram.fXaxis); Object.assign(hist.fYaxis, eff.fTotalHistogram.fYaxis); hist.fName = 'eff_histo'; return hist; } /** @summary Fill graph with points from efficiency object */ fillGraph(gr, opt) { const eff = this.getObject(), xaxis = eff.fTotalHistogram.fXaxis, npoints = xaxis.fNbins, plot0Bins = (opt.indexOf('e0') >= 0); for (let n = 0, j = 0; n < npoints; ++n) { if (!plot0Bins && eff.fTotalHistogram.getBinContent(n + 1) === 0) continue; const value = this.getEfficiency(eff, n + 1); gr.fX[j] = xaxis.GetBinCenter(n + 1); gr.fY[j] = value; gr.fEXlow[j] = xaxis.GetBinCenter(n + 1) - xaxis.GetBinLowEdge(n + 1); gr.fEXhigh[j] = xaxis.GetBinLowEdge(n + 2) - xaxis.GetBinCenter(n + 1); gr.fEYlow[j] = this.getEfficiencyErrorLow(eff, n + 1, value); gr.fEYhigh[j] = this.getEfficiencyErrorUp(eff, n + 1, value); gr.fNpoints = ++j; } gr.fTitle = eff.fTitle; this.copyAttributes(gr, eff); } /** @summary Fill graph with points from efficiency object */ fillHisto(hist) { const eff = this.getObject(), nbinsx = hist.fXaxis.fNbins, nbinsy = hist.fYaxis.fNbins; for (let i = 0; i < nbinsx + 2; ++i) { for (let j = 0; j < nbinsy + 2; ++j) { const bin = hist.getBin(i, j); hist.fArray[bin] = this.getEfficiency(eff, bin); } } hist.fTitle = eff.fTitle; hist.fBits |= kNoStats; this.copyAttributes(hist, eff); } /** @summary Draw function */ drawFunction(indx) { const eff = this.getObject(); if (!eff?.fFunctions || (indx >= eff.fFunctions.arr.length)) return this; return TF1Painter.draw(this.getPadPainter(), eff.fFunctions.arr[indx], eff.fFunctions.opt[indx]) .then(funcp => { funcp?.setSecondaryId(this, `func_${indx}`); return this.drawFunction(indx + 1); }); } /** @summary Fill context menu */ fillContextMenuItems(menu) { menu.addRedrawMenu(this); } /** @summary Fully redraw efficiency with new draw options */ async redrawWith(opt, skip_cleanup) { if (!skip_cleanup) this.getPadPainter()?.removePrimitive(this, true); if (!opt || !isStr(opt)) opt = ''; opt = opt.toLowerCase(); let promise, draw_total = false; const eff = this.getObject(), dom = this.getDrawDom(); if (opt[0] === 'b') { draw_total = true; promise = (this.ndim === 1 ? TH1Painter : TH2Painter).draw(dom, eff.fTotalHistogram, opt.slice(1)); } else if (this.ndim === 1) { if (!opt) opt = 'ap'; if ((opt.indexOf('same') < 0) && (opt.indexOf('a') < 0)) opt += 'a'; if (opt.indexOf('p') < 0) opt += 'p'; const gr = this.createGraph(eff); this.fillGraph(gr, opt); promise = TGraphPainter.draw(dom, gr, opt); } else { if (!opt) opt = 'col'; const hist = this.createHisto(eff); this.fillHisto(hist, opt); promise = TH2Painter.draw(dom, hist, opt); } return promise.then(subp => { subp?.setSecondaryId(this, 'eff'); this.addToPadPrimitives(); return draw_total ? this : this.drawFunction(0); }); } /** @summary Draw TEfficiency object */ static async draw(dom, eff, opt) { if (!eff || !eff.fTotalHistogram) return null; const painter = new TEfficiencyPainter(dom, eff); if (eff.fTotalHistogram._typename.indexOf(clTH1) === 0) painter.ndim = 1; else if (eff.fTotalHistogram._typename.indexOf(clTH2) === 0) painter.ndim = 2; else return null; painter.fBoundary = getTEfficiencyBoundaryFunc(eff.fStatisticOption, eff.TestBit(kIsBayesian)); return painter.redrawWith(opt, true); } } // class TEfficiencyPainter export { TEfficiencyPainter };