+
- {{ workspace_store.channels[channel_index].title }}
+ {{ channel.title }}
diff --git a/src/components/charts/SystematicChart.vue b/src/components/charts/SystematicChart.vue
new file mode 100644
index 0000000..97b8729
--- /dev/null
+++ b/src/components/charts/SystematicChart.vue
@@ -0,0 +1,264 @@
+
+
+
+
+
+ {{ channel.title }}
+
+
+
+
+
+
+
diff --git a/src/interfaces.ts b/src/interfaces.ts
index a2f3e74..92922e1 100644
--- a/src/interfaces.ts
+++ b/src/interfaces.ts
@@ -31,6 +31,11 @@ export interface IFitResults {
labels: string[];
}
+export interface INPs {
+ bestfit: number[];
+ labels: string[];
+}
+
export interface ITaskResults {
ready: boolean;
successful: boolean;
@@ -51,7 +56,7 @@ export interface IModifier {
export interface INormSys {
hi: number;
- low: number;
+ lo: number;
}
export interface IHistoSys {
@@ -76,11 +81,17 @@ export interface IChannelGetters {
stacked_data: IStackedChannel;
normalized_stacked_data: IStackedChannel;
stacked_data_per_bin: IStackedChannelBinwise;
- yield_of_process: (process_name: string) => number;
+ stacked_data_per_bin_postfit: IStackedChannelBinwise;
+ yield_of_process: (process_name: string, postfit: boolean) => number;
title: string;
modifier_names: string[];
modifier_types: { [key: string]: { [key: string]: string } };
+ modifier_yields: {
+ [key: string]: { [key: string]: { [key: string]: number[] } };
+ };
+ normfactor: (process: IProcess, postfit: boolean) => number;
normfactor_names: string[];
+ number_of_bins: number;
}
export interface IChannelActions {
@@ -128,6 +139,7 @@ export interface IWorkspaceState {
fitresults: IFitResults;
fitted: boolean;
fitting: boolean;
+ nps: IFitResults;
result_id: string;
channels: (IChannelState & IChannelGetters & IChannelActions)[];
}
@@ -135,6 +147,8 @@ export interface IWorkspaceState {
export interface IWorkspaceGetters {
process_names: string[];
modifier_names: string[];
+ normfactors: { [key: string]: INormFactor };
+ number_of_processes: number;
}
export interface IWorkspaceActions {
@@ -165,7 +179,8 @@ export interface IStackedChannelBinwise {
// {name: process2, low: low2, high: high2},
// ...],
// ...
- // ]
+ // ],
+ // data: data1,
// }
name: string;
content: Array
>;
@@ -175,6 +190,7 @@ export interface IStackedChannelBinwise {
export interface INormFactor {
name: string;
fixed: boolean;
+ value: number;
}
export interface IUncertaintyPerSystematic {
diff --git a/src/stores/channel.ts b/src/stores/channel.ts
index c3b6a46..3c23d69 100644
--- a/src/stores/channel.ts
+++ b/src/stores/channel.ts
@@ -26,24 +26,23 @@ export const useChannelStore = function (id: number, channel: string) {
normfactor_names(): string[] {
const factors: string[] = [];
for (const sample of this.samples) {
- for (const modifier of sample.modifiers) {
- if (modifier.type !== 'normfactor') {
- continue;
- }
- factors.push(modifier.name);
- }
+ factors.push(
+ ...sample.modifiers
+ .filter((modifier) => modifier.type === 'normfactor')
+ .map((modifier) => modifier.name)
+ .filter((name) => !factors.includes(name))
+ );
}
- return [...new Set(factors)];
+ return factors;
},
modifier_names(): string[] {
const names: string[] = [];
for (const sample of this.samples) {
- for (const modifier of sample.modifiers) {
- if (names.includes(modifier.name)) {
- continue;
- }
- names.push(modifier.name);
- }
+ names.push(
+ ...sample.modifiers
+ .filter((modifier) => !names.includes(modifier.name))
+ .map((modifier) => modifier.name)
+ );
}
return names;
},
@@ -78,17 +77,202 @@ export const useChannelStore = function (id: number, channel: string) {
}
return types;
},
- yield_of_process(state): (process_name: string) => number {
- // returns the overall yield of a given process
- return function (process_name: string): number {
- let yields = 0;
- for (const p of state.samples) {
- if (p.name !== process_name) {
+ modifier_yields(): {
+ [key: string]: { [key: string]: { [key: string]: number[] } };
+ } {
+ const yields = {} as {
+ [key: string]: { [key: string]: { [key: string]: number[] } };
+ };
+ for (const sample of this.samples) {
+ const yields_per_sample = {} as {
+ [key: string]: { [key: string]: number[] };
+ };
+ for (const modifier_name of this.workspace_store.modifier_names) {
+ yields_per_sample[modifier_name] = {};
+ const modifier_type =
+ this.modifier_types[sample.name][modifier_name];
+ // first check if the modifier is present
+ // for this specific sample in this channel
+ if (modifier_type === 'none') {
+ yields_per_sample[modifier_name]['up'] = new Array(
+ this.number_of_bins
+ ).fill(0);
+ yields_per_sample[modifier_name]['down'] = new Array(
+ this.number_of_bins
+ ).fill(0);
+ continue;
+ }
+ const modifier = sample.modifiers.find(
+ (m) => m.name === modifier_name
+ );
+ if (!modifier) {
+ console.log(
+ 'Could not find modifier ' +
+ modifier_name +
+ ' for sample ' +
+ sample.name +
+ ' in channel ' +
+ this.name
+ );
+ continue;
+ }
+ // do normsys next
+ if (modifier_type === 'normsys') {
+ yields_per_sample[modifier_name]['up'] = sample.data.map(
+ (x) =>
+ x * ((modifier.data as { hi: number; lo: number }).hi - 1)
+ );
+ yields_per_sample[modifier_name]['down'] = sample.data.map(
+ (x) =>
+ x * ((modifier.data as { hi: number; lo: number }).lo - 1)
+ );
continue;
}
- yields = p.data.reduce((pv, cv) => pv + cv, 0);
+ if (modifier_type === 'histosys') {
+ yields_per_sample[modifier_name]['up'] = sample.data.map(
+ (x, i) =>
+ (modifier.data as { hi_data: number[]; lo_data: number[] })
+ .hi_data[i] - x
+ );
+ yields_per_sample[modifier_name]['down'] = sample.data.map(
+ (x, i) =>
+ x -
+ (modifier.data as { hi_data: number[]; lo_data: number[] })
+ .lo_data[i]
+ );
+ continue;
+ }
+ if (modifier_type === 'normhisto') {
+ const second_modifier = sample.modifiers.filter(
+ (m) => m.name === modifier_name
+ )[1];
+ const norm_modifier =
+ second_modifier.type === 'normsys' ? second_modifier : modifier;
+ const histo_modifier =
+ second_modifier.type === 'histosys'
+ ? second_modifier
+ : modifier;
+ yields_per_sample[modifier_name]['up'] = sample.data.map(
+ (x, i) =>
+ (
+ histo_modifier.data as {
+ hi_data: number[];
+ lo_data: number[];
+ }
+ ).hi_data[i] - x
+ );
+ yields_per_sample[modifier_name]['down'] = sample.data.map(
+ (x, i) =>
+ x -
+ (
+ histo_modifier.data as {
+ hi_data: number[];
+ lo_data: number[];
+ }
+ ).lo_data[i]
+ );
+ yields_per_sample[modifier_name]['up'] = yields_per_sample[
+ modifier_name
+ ]['up'].map(
+ (x) =>
+ x *
+ ((norm_modifier.data as { hi: number; lo: number }).hi - 1)
+ );
+ yields_per_sample[modifier_name]['down'] = yields_per_sample[
+ modifier_name
+ ]['down'].map(
+ (x) =>
+ x *
+ ((norm_modifier.data as { hi: number; lo: number }).lo - 1)
+ );
+ continue;
+ }
+ // for lumi, normfactors, staterrors do nothing for now
+ yields_per_sample[modifier_name]['up'] = new Array(
+ this.number_of_bins
+ ).fill(0);
+ yields_per_sample[modifier_name]['down'] = new Array(
+ this.number_of_bins
+ ).fill(0);
+ }
+ yields[sample.name] = yields_per_sample;
+ }
+ return yields;
+ },
+ normfactor(state) {
+ return (process: IProcess, postfit: boolean): number => {
+ let factor = 1.0;
+ for (const key in state.workspace_store.normfactors) {
+ const normfactor = state.workspace_store.normfactors[key];
+ if (
+ !process.modifiers.find((m) => {
+ return m.name === key;
+ })
+ )
+ continue;
+ if (!postfit) {
+ factor = factor * normfactor.value;
+ } else {
+ const np_index: number = state.workspace_store.nps
+ ? state.workspace_store.nps.labels.indexOf(key)
+ : -1;
+ factor =
+ factor *
+ (state.workspace_store.nps
+ ? state.workspace_store.nps.bestfit[np_index]
+ : 1);
+ }
+ }
+ return factor;
+ };
+ },
+ pulleffects(): (process_name: string, bin: number) => number {
+ return (process_name: string, bin: number): number => {
+ const sample = this.samples.find((s) => s.name === process_name);
+ if (!sample) {
+ console.log('Could not find sample with name.');
+ return 1;
+ }
+ const inverse_nominal_yields = 1 / sample.data[bin];
+ const modified_yields = this.modifier_yields[process_name];
+ let factor = 1.0;
+ let modifier_index = 0;
+ for (const modifier_name of this.workspace_store.nps.labels) {
+ // need to filter out lumi, staterror, normfactor
+ const modifier_type =
+ this.modifier_types[process_name][modifier_name];
+ if (
+ modifier_type !== undefined &&
+ modifier_type !== 'lumi' &&
+ modifier_type !== 'staterror' &&
+ modifier_type !== 'normfactor'
+ ) {
+ factor *=
+ 1 +
+ this.workspace_store.nps.bestfit[modifier_index] *
+ modified_yields[modifier_name]['up'][bin] *
+ inverse_nominal_yields;
+ }
+ modifier_index += 1;
}
- return yields;
+ return factor;
+ };
+ },
+ yield_of_process(
+ state
+ ): (process_name: string, postfit: boolean) => number {
+ // returns the overall yield of a given process
+ return (process_name: string, postfit: boolean): number => {
+ const p = state.samples.find((s) => {
+ return s.name === process_name;
+ });
+ if (!p) {
+ console.log('Could not find process ' + process_name);
+ return 0;
+ }
+ return (
+ p.data.reduce((pv, cv) => pv + cv, 0) * this.normfactor(p, postfit)
+ );
};
},
stacked_data(): IStackedChannel {
@@ -99,7 +283,7 @@ export const useChannelStore = function (id: number, channel: string) {
for (const process_name of this.workspace_store.process_names) {
const process = {} as IStackedProcess;
process.name = process_name;
- const yields = this.yield_of_process(process_name);
+ const yields = this.yield_of_process(process_name, false);
process.low = previous_high;
process.high = previous_high + yields;
processes.push(process);
@@ -110,11 +294,11 @@ export const useChannelStore = function (id: number, channel: string) {
},
normalized_stacked_data(): IStackedChannel {
// returns a stack of overall relative yields of the different processes in the different channels
- let total_yield = 0;
const normalized_stacked_data = this.stacked_data;
- for (const p of normalized_stacked_data.processes) {
- total_yield += p.high - p.low;
- }
+ const total_yield =
+ this.stacked_data.processes[
+ this.workspace_store.number_of_processes - 1
+ ].high;
for (const p of normalized_stacked_data.processes) {
p.high = (p.high / total_yield) * 100;
p.low = (p.low / total_yield) * 100;
@@ -126,7 +310,7 @@ export const useChannelStore = function (id: number, channel: string) {
channel.name = this.name;
channel.data = this.observations;
channel.content = [];
- const bin_number = this.samples[0].data.length;
+ const bin_number = this.number_of_bins;
for (let i_bin = 0; i_bin < bin_number; i_bin++) {
const processes = [] as IStackedProcess[];
let previous_high = 0;
@@ -135,17 +319,16 @@ export const useChannelStore = function (id: number, channel: string) {
process.name = process_name;
process.low = previous_high;
// find process_index from name in samples
- const process_index = this.samples
- .map(function (e) {
- return e.name;
- })
- .indexOf(process_name);
+ const sample = this.samples.find((s) => {
+ return s.name === process_name;
+ });
// in case a process is not available in this channel, set yields to zero
- if (process_index === -1) {
+ if (!sample) {
process.high = previous_high;
} else {
process.high =
- previous_high + this.samples[process_index].data[i_bin];
+ previous_high +
+ sample.data[i_bin] * this.normfactor(sample, false);
}
processes.push(process);
previous_high = process.high;
@@ -154,6 +337,42 @@ export const useChannelStore = function (id: number, channel: string) {
}
return channel;
},
+ stacked_data_per_bin_postfit(): IStackedChannelBinwise {
+ const channel = {} as IStackedChannelBinwise;
+ channel.name = this.name;
+ channel.data = this.observations;
+ channel.content = [];
+ const bin_number = this.number_of_bins;
+ for (let i_bin = 0; i_bin < bin_number; i_bin++) {
+ const processes = [] as IStackedProcess[];
+ let previous_high = 0;
+ for (const process_name of this.workspace_store.process_names) {
+ const process = {} as IStackedProcess;
+ process.name = process_name;
+ process.low = previous_high;
+ const sample = this.samples.find((s) => {
+ return s.name === process_name;
+ });
+ // in case a process is not available in this channel, set yields to zero
+ if (!sample) {
+ process.high = previous_high;
+ } else {
+ process.high =
+ previous_high +
+ sample.data[i_bin] *
+ Math.max(0, this.normfactor(sample, true)) *
+ this.pulleffects(process.name, i_bin);
+ }
+ processes.push(process);
+ previous_high = process.high;
+ }
+ channel.content.push(processes);
+ }
+ return channel;
+ },
+ number_of_bins(): number {
+ return this.samples[0].data.length;
+ },
},
actions: {
cleanup(): void {
diff --git a/src/stores/workspace.ts b/src/stores/workspace.ts
index d071c7e..81d8d1f 100644
--- a/src/stores/workspace.ts
+++ b/src/stores/workspace.ts
@@ -4,6 +4,7 @@ import { useChannelStore } from 'src/stores/channel';
import {
IAnalysis,
IFitResults,
+ INormFactor,
ITaskResults,
IWorkspace,
IWorkspaceState,
@@ -25,6 +26,7 @@ export const useWorkspaceStore = function (id: number) {
fitresults: {} as IFitResults,
fitted: false as boolean,
fitting: false as boolean,
+ nps: {} as IFitResults,
result_id: '',
channels: [] as IChannel[],
} as IWorkspaceState),
@@ -50,12 +52,10 @@ export const useWorkspaceStore = function (id: number) {
process_titles(state): { [key: string]: string } {
const process_titles: { [key: string]: string } = {};
for (const process_name of this.process_names) {
- if (process_name in state.process_title_index) {
- process_titles[process_name] =
- state.process_title_index[process_name];
- } else {
- process_titles[process_name] = process_name;
- }
+ process_titles[process_name] =
+ process_name in state.process_title_index
+ ? state.process_title_index[process_name]
+ : process_name;
}
return process_titles;
},
@@ -65,19 +65,22 @@ export const useWorkspaceStore = function (id: number) {
for (const p of c.samples) {
// set initial yields to zero if key does not exist yet
process_yields[p.name] =
- (process_yields[p.name] || 0) + c.yield_of_process(p.name);
+ (process_yields[p.name] || 0) + c.yield_of_process(p.name, false);
}
}
return process_yields;
},
normfactors(state) {
- let normfactor_names = [];
+ const normfactor_names: string[] = [];
for (const channel of state.channels) {
- normfactor_names.push(...channel.normfactor_names);
+ normfactor_names.push(
+ ...channel.normfactor_names.filter(
+ (name) => !normfactor_names.includes(name)
+ )
+ );
}
- normfactor_names = [...new Set(normfactor_names)];
- const factors = [];
+ const factors = {} as { [key: string]: INormFactor };
for (const normfactor_name of normfactor_names) {
const parameter =
state.workspace.measurements[0].config.parameters.find((p) => {
@@ -89,11 +92,11 @@ export const useWorkspaceStore = function (id: number) {
);
continue;
}
- const factor = {
+ factors[normfactor_name] = {
name: normfactor_name,
fixed: parameter.fixed !== undefined && parameter.fixed,
+ value: parameter.inits ? parameter.inits[0] : 1.0,
};
- factors.push(factor);
}
return factors;
},
@@ -105,22 +108,21 @@ export const useWorkspaceStore = function (id: number) {
process_index++
) {
const process_name = this.process_names[process_index];
- if (process_name in this.process_color_index) {
- color_per_process[process_name] =
- this.process_color_index[process_name];
- } else {
- color_per_process[process_name] =
- color_scheme[process_index % color_scheme.length];
- }
+ color_per_process[process_name] =
+ process_name in this.process_color_index
+ ? this.process_color_index[process_name]
+ : color_scheme[process_index % color_scheme.length];
}
return color_per_process;
},
modifier_names(): string[] {
const names = [] as string[];
for (const channel of this.channels) {
- names.push(...channel.modifier_names);
+ names.push(
+ ...channel.modifier_names.filter((name) => !names.includes(name))
+ );
}
- return [...new Set(names)];
+ return names;
},
number_of_processes(): number {
return this.process_names.length;
@@ -198,6 +200,7 @@ export const useWorkspaceStore = function (id: number) {
this.fitting = false;
this.result_id = '';
this.fitresults = {} as IFitResults;
+ this.nps = {} as IFitResults;
this.process_title_index = {} as { [key: string]: string };
this.process_color_index = {} as { [key: string]: string };
this.download_urls = {} as { [key: string]: string };
@@ -207,6 +210,10 @@ export const useWorkspaceStore = function (id: number) {
this.channels = [];
},
async get_fit_results(): Promise {
+ if (this.fitted) {
+ this.nps.bestfit = [...this.fitresults.bestfit];
+ return;
+ }
this.fitting = true;
const url =
'https://workspaceexplorerbackend-workspaceexplorerbackend.app.cern.ch/api/v1/workspace';
@@ -232,6 +239,10 @@ export const useWorkspaceStore = function (id: number) {
.then((data) => (response_data = data));
if (response_data.ready) {
this.fitresults = response_data.value;
+ this.nps.bestfit = [...this.fitresults.bestfit];
+ this.nps.uncertainty = [...this.fitresults.uncertainty];
+ this.nps.labels = [...this.fitresults.labels];
+ this.nps.correlations = [...this.fitresults.correlations];
this.fitted = true;
this.fitting = false;
} else {
diff --git a/src/utils/plots.ts b/src/utils/plots.ts
index a86bb10..8a9551d 100644
--- a/src/utils/plots.ts
+++ b/src/utils/plots.ts
@@ -34,3 +34,7 @@ export function axis_path(
export function round_to_n_digits(value: number, n: number): number {
return Math.round(value * 10 ** n) / 10 ** n;
}
+
+export function number_of_zeroes(x: number): number {
+ return Math.floor(Math.log10(x));
+}