From 58d45de0be713563621653d8d9bcd9fb1e7aae25 Mon Sep 17 00:00:00 2001 From: Frank Sauerburger <frank@sauerburger.com> Date: Sat, 9 Jan 2021 15:12:38 +0100 Subject: [PATCH] Add systematic envelops --- uhepp-js/src/components/UheppHist.jsx | 25 +++++--- uhepp-js/src/components/UheppHistUI.jsx | 78 ++++++++++++++++++++++--- uhepp-js/src/helpers/uhepp.js | 45 +++++++++++--- uhepp-js/tests/helpers.js | 58 +++++++++++++++++- 4 files changed, 179 insertions(+), 27 deletions(-) diff --git a/uhepp-js/src/components/UheppHist.jsx b/uhepp-js/src/components/UheppHist.jsx index d15e1c0..b4df53b 100644 --- a/uhepp-js/src/components/UheppHist.jsx +++ b/uhepp-js/src/components/UheppHist.jsx @@ -559,6 +559,22 @@ const Legend = ({ } const UheppHist = ({width, height, uhepp}) => { + const post_uhepp = Object.assign({}, uhepp, { + yields: preprocessData({ + yields: uhepp.yields, + old_edges: uhepp.bins.edges, + new_edges: uhepp.bins.rebin, + include_underflow: uhepp.bins.include_underflow, + include_overflow: uhepp.bins.include_overflow, + }) + }) + + + return <UheppHistPost width={width} height={height} uhepp={post_uhepp} /> +} + +const UheppHistPost = ({width, height, uhepp}) => { + const post_uhepp = uhepp // backwards compatibility const [highlightedBin, setHighlightedBin] = useState(null); const [showTotal, setShowTotal] = useState(false); @@ -577,15 +593,6 @@ const UheppHist = ({width, height, uhepp}) => { "#1f77b4", ]) - const post_uhepp = Object.assign({}, uhepp, { - yields: preprocessData({ - yields: uhepp.yields, - old_edges: uhepp.bins.edges, - new_edges: uhepp.bins.rebin, - include_underflow: uhepp.bins.include_underflow, - include_overflow: uhepp.bins.include_overflow, - }) - }) const margin = { top: 10, diff --git a/uhepp-js/src/components/UheppHistUI.jsx b/uhepp-js/src/components/UheppHistUI.jsx index 333c00e..defa1bd 100644 --- a/uhepp-js/src/components/UheppHistUI.jsx +++ b/uhepp-js/src/components/UheppHistUI.jsx @@ -1,6 +1,37 @@ import React, { useState } from "react"; import UheppHist from "./UheppHist.jsx"; +const noVariationUsed = (uhepp) => { + const main_names = uhepp.stacks.map(stack => stack.content.map(item => item.yields)).flat() + const num_names = uhepp.ratio.map(item => item.numerator).flat() + const den_names = uhepp.ratio.map(item => item.denominator).flat() + const names = [main_names, den_names, num_names].flat() + + const varied_names = names.filter(name => (name && name.indexOf("/") != -1)) + return varied_names.length == 0 +} + +const variationList = (uhepp) => { + return Array.from(new Set(Object.entries(uhepp.yields).map(([key, item]) => [ + ...Object.keys(item.var_up || {}), + ...Object.keys(item.var_down || {}) + ]).flat())).sort() +} + +const makeVariationStacks = (uhepp, variation, updown) => { + return uhepp.stacks.map(stack => + ({ + type: "step", + content: [{ + "label": updown, + "yield": stack.content.map(item => + item.yield.map(name => `${name}/${variation}/${updown}`) + ).flat() + }] + }) + ) +} + const UheppHistUI = ({width, height, uhepp}) => { const [uhepp_data, setData] = useState(uhepp) @@ -30,7 +61,22 @@ const UheppHistUI = ({width, height, uhepp}) => { )) } - let rebin = uhepp_data.bins.rebin || uhepp_data.bins.edges + const handleEnvelop = (e) => { + let variationName = e.target.value + setData(Object.assign({}, uhepp_data, + {stacks: [ + ...uhepp.stacks, + ...makeVariationStacks(uhepp, variationName, "up"), + ...makeVariationStacks(uhepp, variationName, "down"), + ]} + )) + + } + + const variations = variationList(uhepp) + const isVariationReady = noVariationUsed(uhepp) && (variations.length > 0) + + const rebin = uhepp_data.bins.rebin || uhepp_data.bins.edges return <> <div>{ Object.entries(uhepp.metadata.tags).map(([key, value]) => value == null ? @@ -49,9 +95,9 @@ const UheppHistUI = ({width, height, uhepp}) => { <li className="nav-item"> <a className="nav-link" id="binning-tab" data-toggle="tab" href="#binning" role="tab" aria-controls="binning" aria-selected="true">Binning</a> </li> - { /*<li className="nav-item"> - <a className="nav-link" id="other-tab" data-toggle="tab" href="#other" role="tab" aria-controls="other" aria-selected="false">Other</a> - </li> */} + { <li className="nav-item"> + <a className="nav-link" id="variations-tab" data-toggle="tab" href="#variations" role="tab" aria-controls="variations" aria-selected="false">Variations</a> + </li> } <li className="nav-item"> <a className="nav-link" id="reset-tab" data-toggle="tab" href="#reset" role="tab" aria-controls="reset" aria-selected="false">Reset</a> </li> @@ -91,9 +137,10 @@ const UheppHistUI = ({width, height, uhepp}) => { <form> <div className="form-group"> <label htmlFor="rebin">Bin edges</label> - <select multiple size={10} className="form-control" id="rebin" onChange={(e) => handleRebin(e)}> + <select multiple size={10} className="form-control" id="rebin" onChange={(e) => handleRebin(e)} + defaultValue={rebin}> {uhepp.bins.edges.map((v, i) => - <option value={v} key={i} selected={rebin.indexOf(v) >= 0}>{v}</option> + <option value={v} key={i}>{v}</option> )} </select> </div> @@ -131,9 +178,22 @@ const UheppHistUI = ({width, height, uhepp}) => { </div> </form> </div> - { /*<div className="tab-pane" id="other" role="tabpanel" aria-labelledby="other-tab"> - <h1>La di da</h1> - </div> */} + <div className="tab-pane p-3" id="variations" role="tabpanel" aria-labelledby="variations-tab"> + { !isVariationReady && + <p>To use the variation feature, you must add variation data to + the <code>yields</code> object. Please note that the stacks must not use + any varied yield in the default view.</p> } + { isVariationReady && + <form> + <div className="form-group"> + <label htmlFor="envelop">Envelop</label> + <select className="form-control" id="envelop" onChange={(e) => handleEnvelop(e)}> + { variations.map((name, i) => <option value={name} key={i}>{name}</option>) } + </select> + </div> + </form> + } + </div> <div className="tab-pane p-3" id="reset" role="tabpanel" aria-labelledby="reset-tab"> <p>Reset all view modifications. Pull the plot to make permanent changes.</p> diff --git a/uhepp-js/src/helpers/uhepp.js b/uhepp-js/src/helpers/uhepp.js index c3a14b3..1bd6590 100644 --- a/uhepp-js/src/helpers/uhepp.js +++ b/uhepp-js/src/helpers/uhepp.js @@ -32,15 +32,35 @@ export const aggregate = ({ bin_index }) => { const getBase = (process_name) => { - if (!yields.hasOwnProperty(process_name)) { - return 0 - } - const process = yields[process_name] - if (!process.hasOwnProperty(property)) { - return 0 + if (process_name.indexOf("/") != -1) { + if (property != "base") { + return 0 + } + const [name, variation, updown] = process_name.split("/") + if (!yields.hasOwnProperty(name)) { + return 0 + } + const process = yields[name] + const var_obj = "var_" + updown + if (!process.hasOwnProperty(var_obj) || !process[var_obj].hasOwnProperty(variation)) { + // Fall back + return aggregate({yields, process_names: [name], property, pre, post, bin_index}) + } + + const base = process[var_obj][variation] + return (bin_index != null) ? base[bin_index] : base; + + } else { + if (!yields.hasOwnProperty(process_name)) { + return 0 + } + const process = yields[process_name] + if (!process.hasOwnProperty(property)) { + return 0 + } + const base = process[property] + return (bin_index != null) ? base[bin_index] : base; } - const base = process[property] - return (bin_index != null) ? base[bin_index] : base; } const agg = process_names.map(getBase).flat().map(pre).reduce((a, b) => a + b, 0) return post(agg) @@ -153,6 +173,8 @@ const fromEntires = (entries) => { return result } +export const objmap = (obj, map) => fromEntires(Object.entries(obj).map(([key, value]) => [key, map(value, key)])) + /** * Processes the yield objects and returns a new version * @@ -178,6 +200,13 @@ export const preprocessData = ({ const rebinned = { "base": rebin(yield_obj.base, old_edges, eff_edges), "stat": yield_obj.stat ? rebin(yield_obj.stat, old_edges, eff_edges, (x) => x*x, (x)=>Math.sqrt(x)) : null, + "syst": yield_obj.syst ? rebin(yield_obj.syst, old_edges, eff_edges, (x) => x*x, (x)=>Math.sqrt(x)) : null, + "var_up": yield_obj.var_up ? objmap(yield_obj.var_up, + (value) => rebin(value, old_edges, eff_edges, (x) => x*x, (x)=>Math.sqrt(x)) + ) : null, + "var_down": yield_obj.var_down ? objmap(yield_obj.var_down, + (value) => rebin(value, old_edges, eff_edges, (x) => x*x, (x)=>Math.sqrt(x)) + ) : null, } if (include_underflow) { rebinned.base[1] += rebinned.base[0] diff --git a/uhepp-js/tests/helpers.js b/uhepp-js/tests/helpers.js index acad4fb..e85cb3c 100644 --- a/uhepp-js/tests/helpers.js +++ b/uhepp-js/tests/helpers.js @@ -1,6 +1,6 @@ import { use, assert, expect } from 'chai'; import chaiAlmost from 'chai-almost'; -import { sumBase, sumStat, rebin, histogramify } from "../src/helpers/uhepp.js"; +import { sumBase, sumStat, rebin, histogramify, objmap } from "../src/helpers/uhepp.js"; use(chaiAlmost()) @@ -69,6 +69,31 @@ describe("sumBase", () => { const sum = sumBase({}, [], 2) assert.equal(sum, 0); }) + + const syst_yield = { + "process_a": { + "base": [ 0, 1, 2, 3, 4, 5], + "stat": [0.1, 0.1, 0.1, 0.1, 0.1], + "var_up": { + "ele_eff": [1, 2, 3, 4, 5, 6], + }, + "var_down": { + "mu_eff": [0, 0, 1, 2, 3, 4], + } + }, + } + it("should return up variations", () => { + const sum = sumBase(syst_yield, ["process_a/ele_eff/up"]) + assert.equal(sum, 21); + }) + it("should return down variations", () => { + const sum = sumBase(syst_yield, ["process_a/mu_eff/down"]) + assert.equal(sum, 10); + }) + it("should return fall back to base", () => { + const sum = sumBase(toy_yield, ["process_a/mu_eff/up"]) + assert.equal(sum, 15); + }) }); describe("sumStat", () => { @@ -136,6 +161,22 @@ describe("sumStat", () => { const sum = sumStat({}, [], 2) assert.equal(sum, 0); }) + const syst_yield = { + "process_a": { + "base": [ 0, 1, 2, 3, 4, 5], + "stat": [0.1, 0.1, 0.1, 0.1, 0.1], + "var_up": { + "ele_eff": [1, 2, 3, 4, 5, 6], + }, + "var_down": { + "mu_eff": [0, 0, 1, 2, 3, 4], + } + }, + } + it("should fall back to zero for variations", () => { + const sum = sumStat(syst_yield, ["process_a/ele_eff/up"]) + assert.equal(sum, 0); + }) }); describe("rebin", () => { @@ -207,3 +248,18 @@ describe("histogramify", () => { }) }) + +describe("objmap", () => { + it("map values", () => { + const obj = {"x": 1, "y": 2} + const derived = objmap(obj, value => value*value) + assert.equal(derived.x, 1) + assert.equal(derived.y, 4) + }) + it("map values based on key", () => { + const obj = {"x": 1, "y": 2} + const derived = objmap(obj, (value, key) => key) + assert.equal(derived.x, "x") + assert.equal(derived.y, "y") + }) +}) -- GitLab