From 8d28810578e1c17b1c9b212023853cd983712453 Mon Sep 17 00:00:00 2001 From: Zeynep Eda Cabukoglu Date: Thu, 16 Jul 2026 17:20:49 +0200 Subject: [PATCH 1/6] Add all-event-weights functor and pp_hhh example analysis --- .../data_source/event_weights/Definitions.h | 132 ++++++++++++++++++ .../event_weights/analysis_stage1.py | 102 ++++++++++++++ 2 files changed, 234 insertions(+) create mode 100644 examples/data_source/event_weights/Definitions.h create mode 100644 examples/data_source/event_weights/analysis_stage1.py diff --git a/examples/data_source/event_weights/Definitions.h b/examples/data_source/event_weights/Definitions.h new file mode 100644 index 00000000000..8dbc4c46f4b --- /dev/null +++ b/examples/data_source/event_weights/Definitions.h @@ -0,0 +1,132 @@ +#ifndef DEFINITIONS_H +#define DEFINITIONS_H + +// podio::DataSource reconstructs the logical EDM4hep EventHeader collection +// from the split ROOT branches. This header defines the input type: +// edm4hep::EventHeaderCollection +#include "edm4hep/EventHeaderCollection.h" + +// This header defines ROOT::VecOps::RVec, +// which we will use as the variable-length array of double weights. +#include + +#include + +//input: EventHeader +//output: weights array +// However, eventheader is split across branches and the weights array is in : "_EventHeader_weights" +//its type: ROOT::VecOps::RVec and I cannot use that as an input, so -> + + +// This is because eventheader data has a fixed size and cannot hold variable length arrays. +//eventheader column type: ROOT::VecOps::RVec + +// Enabling podio::DataSource reconstructs the logical +// edm4hep::EventHeaderCollection from the split ROOT branches. +// +// Therefore: +// input type = edm4hep::EventHeaderCollection +// output type = ROOT::VecOps::RVec + // which is array of double values (weights) + + + + +//functor 1 : GetAllWeights + + struct GetAllWeights { +//return type: (array of double values (weights) ) + ROOT::VecOps::RVec + + + +// creating the function: +//operator() makes struct behave like a function +// first paranthesis is the input, (input type) + // const inside the paranthesis means -> + // const inside the paranthesis means -> This function may read eventHeaders, but it may not modify it. +// const{ } specifies that calling this functor will not modify the functor object itself: (functor object is defined while calling the functor ex: GetAllWeights A; -> A is the object) + operator()(const edm4hep::EventHeaderCollection& eventHeaders)const{ +//to prevent errors when trying to reach the elements, if empty -> error +// to prevent errors when trying to reach the elements, if empty -> error + if (eventHeaders.empty()) { + return {}; + } + + //from the event header collection for an event we need to take the event header. + // With podio::DataSource, EventHeader becomes an edm4hep::EventHeaderCollection; EDM4hep stores objects in collections by default, even if there is normally only one EventHeader per event. + + const auto& eventHeader = eventHeaders.at(0); + //eventHeaders.at(0) -> gets the first element (event header is the only object in the collection, so first element is the header for the current event.) + //const since + // auto lets C++ use the type returned by eventHeaders.at(0). + + + // Create an empty array where the event weights will be stored. + ROOT::VecOps::RVec weights; + //output type = ROOT::VecOps::RVec (a variable-length array whose elements are doubles) (weights -> variable name) + + //Now we need to extract the weights from the single eventHeader + const auto eventWeights = eventHeader.getWeights(); + //// getWeights() is an EDM4hep function that gets all weights stored in this EventHeader. + + + //Now copy each value from eventWeights into the output array weights + + + + + for (const auto weight : eventWeights) { + // Automatically iterates over all elements without needing the array/vector size. + //For each iteration, the current value is temporarily called weight + + weights.push_back(weight); + //push_back() adds the current weight in the iteration to the end of the weights output array. + } + + return weights; + } + + }; + + + + + +// FUNCTOR 2 : GetWeightByName +// +//Finds a weight by name and returns its value for each event, or -1 if unavailable. +// Finds the weight's index by name once, since the index is the same for all events, then returns that weight's value for each event. + +// Setup inputs (asked once): +// requested weight name: std::string +// available weight names: std::vector + +//// Event input: event header +// edm4hep::EventHeaderCollection + +// Output: +// double value or -1.0 if unavailable + //GetWeightByName("PDF_12")(EventHeader) + //"PDF_12" configures the functor once. + //EventHeader is passed for each event. + +struct GetWeightByName { + + + + +}; + + + + + + + + +#endif + + + + diff --git a/examples/data_source/event_weights/analysis_stage1.py b/examples/data_source/event_weights/analysis_stage1.py new file mode 100644 index 00000000000..6108a966a99 --- /dev/null +++ b/examples/data_source/event_weights/analysis_stage1.py @@ -0,0 +1,102 @@ +''' +Analysis example, measure Higgs mass in the Z(mumu)H recoil measurement. +''' +from argparse import ArgumentParser + + +# Mandatory: Analysis class where the user defines the operations on the +# dataframe. +class Analysis(): + ''' + Higgs mass recoil analysis in Z(mumu)H. + ''' + def __init__(self, cmdline_args): + # Parse additional arguments not known to the FCCAnalyses parsers. + # All command line arguments are provided in the `cmdline_arg` + # dictionary and arguments after "--" are stored under "remaining" key. + parser = ArgumentParser( + description='Additional analysis arguments', + usage='Provided after "--"') + parser.add_argument('--muon-pt', default='10.', type=float, + help='Minimal pT of the mouns.') + self.ana_args, _ = parser.parse_known_args(cmdline_args['remaining']) + + # Mandatory: List of datasets used in the analysis + self.process_list = { + # Run over the full statistics and save it to one output file named + # /.root + 'p8_ee_ZZ_ecm240': {'fraction': 1.}, + # Run over 50% of the statistics and save output into two files + # named /p8_ee_WW_ecm240/chunk.root + # Number of input files needs to be larger that number of chunks + 'p8_ee_WW_ecm240': {'fraction': 0.5, 'chunks': 2}, + # Run over 20% of the statistics and save output into one file + # named /p8_ee_ZH_ecm240_out_f02.root + 'p8_ee_ZH_ecm240': {'fraction': 0.2, + 'output': 'p8_ee_ZH_ecm240_out_f02'} + } + + # Mandatory: Production tag when running over the centrally produced + # samples (this points to the yaml file for getting sample statistics) + # self.prod_tag = 'FCCee/spring2021/IDEA/' + # or Input directory when not running over the centrally produced + # samples. + self.input_dir = '/eos/experiment/fcc/hh/tutorials/' \ + 'edm4hep_tutorial_data/' + + # Optional: output directory, default is local running directory + self.output_dir = 'outputs/FCCee/higgs/mH-recoil/mumu/' \ + f'stage1_{self.ana_args.muon_pt}' + + #podio + #line below tells FCCAnalyses to read the ROOT file using podio::DataSource instead of reading the raw ROOT branches directly + #so that event header isn't split like: EventHeader + # EventHeader.weights_begin + # EventHeader.weights_end + # EventHeader_weights + # With it enabled, podio reconstructs those pieces into a EDM4hep collection + #use_data_source=False → raw split ROOT data; use_data_source=True →podio reconstructs them into edm4hep::EventHeaderCollection. + self.use_data_source = True + + + + + + + + + + + + + # Optional: analysis name, default is '' + # self.analysis_name = 'My Analysis' + + # Optional: number of threads to run on, default is 1 + # self.n_threads = 4 + + # Optional: providing additional analyzers + # self.include_paths = ['additional_analyzers.h'] + self.include_paths = ["Definitions.h"] + + # Optional: test file + self.test_file = '/afs/cern.ch/user/z/zcabukog/event_weights_project/pp_hhh_84TeV_weights_5evt.edm4hep.root' + + # Mandatory: analyzers function to define the analysis graph, please make + # sure you return the dataframe, in this example it is dframe2 + def analyzers(self, dframe): + #define creates a new column in the dataframe, and the first argument is the name of the new column, and the second argument is the function that will be used to create the new column. + dframe2 = dframe.Define( + "event_weights", + "GetAllWeights{}(EventHeader)" + ) + + return dframe2 + # Pass EventHeader into the GetAllWeights functor and store its returned weights in a new column called event_weights. + + + + # Mandatory: output function, please make sure you return the branch list + # as a python list + def output(self): + return ["event_weights"] From 3c3ec3fa43d9bf3718d768779cd397935badb86c Mon Sep 17 00:00:00 2001 From: Zeynep Eda Cabukoglu Date: Sun, 19 Jul 2026 15:28:41 +0200 Subject: [PATCH 2/6] Add weight selection by label --- .../data_source/event_weights/Definitions.h | 176 +++++++++++++----- 1 file changed, 134 insertions(+), 42 deletions(-) diff --git a/examples/data_source/event_weights/Definitions.h b/examples/data_source/event_weights/Definitions.h index 8dbc4c46f4b..de89c36d8e2 100644 --- a/examples/data_source/event_weights/Definitions.h +++ b/examples/data_source/event_weights/Definitions.h @@ -11,6 +11,8 @@ #include #include +#include "podio/Reader.h" +#include //input: EventHeader //output: weights array @@ -27,67 +29,67 @@ // Therefore: // input type = edm4hep::EventHeaderCollection // output type = ROOT::VecOps::RVec - // which is array of double values (weights) +// which is array of double values (weights) //functor 1 : GetAllWeights - struct GetAllWeights { -//return type: (array of double values (weights) ) - ROOT::VecOps::RVec +struct GetAllWeights { + //return type: (array of double values (weights) ) + ROOT::VecOps::RVec -// creating the function: -//operator() makes struct behave like a function -// first paranthesis is the input, (input type) - // const inside the paranthesis means -> - // const inside the paranthesis means -> This function may read eventHeaders, but it may not modify it. -// const{ } specifies that calling this functor will not modify the functor object itself: (functor object is defined while calling the functor ex: GetAllWeights A; -> A is the object) - operator()(const edm4hep::EventHeaderCollection& eventHeaders)const{ -//to prevent errors when trying to reach the elements, if empty -> error -// to prevent errors when trying to reach the elements, if empty -> error - if (eventHeaders.empty()) { + // creating the function: + //operator() makes struct behave like a function + // first paranthesis is the input, (input type) + // const inside the paranthesis means -> + // const inside the paranthesis means -> This function may read eventHeaders, but it may not modify it. + // const{ } specifies that calling this functor will not modify the functor object itself: (functor object is defined while calling the functor ex: GetAllWeights A; -> A is the object) + operator()(const edm4hep::EventHeaderCollection& eventHeaders)const{ + //to prevent errors when trying to reach the elements, if empty -> error + // to prevent errors when trying to reach the elements, if empty -> error + if (eventHeaders.empty()) { return {}; - } + } - //from the event header collection for an event we need to take the event header. - // With podio::DataSource, EventHeader becomes an edm4hep::EventHeaderCollection; EDM4hep stores objects in collections by default, even if there is normally only one EventHeader per event. + //from the event header collection for an event we need to take the event header. + // With podio::DataSource, EventHeader becomes an edm4hep::EventHeaderCollection; EDM4hep stores objects in collections by default, even if there is normally only one EventHeader per event. - const auto& eventHeader = eventHeaders.at(0); - //eventHeaders.at(0) -> gets the first element (event header is the only object in the collection, so first element is the header for the current event.) - //const since - // auto lets C++ use the type returned by eventHeaders.at(0). + const auto& eventHeader = eventHeaders.at(0); + //eventHeaders.at(0) -> gets the first element (event header is the only object in the collection, so first element is the header for the current event.) + //const since + // auto lets C++ use the type returned by eventHeaders.at(0). - // Create an empty array where the event weights will be stored. - ROOT::VecOps::RVec weights; - //output type = ROOT::VecOps::RVec (a variable-length array whose elements are doubles) (weights -> variable name) + // Create an empty array where the event weights will be stored. + ROOT::VecOps::RVec weights; + //output type = ROOT::VecOps::RVec (a variable-length array whose elements are doubles) (weights -> variable name) - //Now we need to extract the weights from the single eventHeader - const auto eventWeights = eventHeader.getWeights(); - //// getWeights() is an EDM4hep function that gets all weights stored in this EventHeader. + //Now we need to extract the weights from the single eventHeader + const auto eventWeights = eventHeader.getWeights(); + //// getWeights() is an EDM4hep function that gets all weights stored in this EventHeader. - //Now copy each value from eventWeights into the output array weights + //Now copy each value from eventWeights into the output array weights - for (const auto weight : eventWeights) { + for (const auto weight : eventWeights) { // Automatically iterates over all elements without needing the array/vector size. //For each iteration, the current value is temporarily called weight - weights.push_back(weight); - //push_back() adds the current weight in the iteration to the end of the weights output array. - } - - return weights; + weights.push_back(weight); + //push_back() adds the current weight in the iteration to the end of the weights output array. } - }; + return weights; + } + +}; @@ -100,33 +102,123 @@ // Setup inputs (asked once): // requested weight name: std::string -// available weight names: std::vector +// input file path: std::string //// Event input: event header // edm4hep::EventHeaderCollection // Output: // double value or -1.0 if unavailable - //GetWeightByName("PDF_12")(EventHeader) - //"PDF_12" configures the functor once. - //EventHeader is passed for each event. + + +// Create one GetWeightByName object and find the requested label's index once. -> GetWeightByName selectedWeight("rwgt_4", inputFile); +// Then call the same object for each event to return that event's selected weight. -> selectedWeight(EventHeader); + +// evetheader is passed for each event, and the functor returns the weight value for that event. struct GetWeightByName { - + // create a variable to store the index of the requested label + // -1 means not found, so if the weight is not found, the functor will return -1 for each event. + int weightIndex{-1}; + //creating a constructor (sets up the object for the struct when it's created) - must be the same name as the struct [ex:GetWeightByName A ("label", input file) -> A is the object] + // We specifically need a constructor because Functor 2 must remember setup information -> requested label and its index + //Without a constructor, the functor would have to search the label list every time operator() runs and operator() is called once for every event -}; + GetWeightByName( + + //inputs to the constructor: input file to find the labels, and the requested label to find its index + //constructor runs whenever a new GetWeightByName object is created + const std::string& requestedWeightName, + // type of the file is string since inputFile is not the file’s contents. It is the path/name used to locate the file. Then PODIO uses that string path to open the actual EDM4hep ROOT file + const std::string& inputFile) + { + // Create the appropriate PODIO reader for the input file format. + // Reader implementation: + // https://github.com/AIDASoft/podio/blob/master/src/Reader.cc + // + // reader is a PODIO object that opens the input file + // and allows us to read its PODIO frames. + // In this project, those frames contain EDM4hep data, + // including the weight labels we want to find. + auto reader = podio::makeReader(inputFile); + + + // Use the reader for the opened input file, select the "metadata" frame category, + + // and read entry 0, which is the first and only metadata entry and contains the weight labels. + auto metadataFrame = reader.readFrame("metadata", 0); + //podio::Frame readFrame(std::string_view name, size_t index, const std::vector& collsToRead = {} + // @param name The category name = "metadata" + // @param index The entry number to read -> podio-dump showed metadata 1 so its index is 0 + // metadataFrame holds the file’s first metadata frame, which contains the EventWeightNames parameter. + //get the list of labels from metadataFrame + auto weightNames = + metadataFrame.getParameter>("EventWeightNames"); + // Extract the EventWeightNames parameter from metadataFrame as a vector of strings and store it in weightNames. + + + // Loop through all weight names, compare each one with the requested name, and store its index when a match is found. + //std::size_t is an unsigned integer type + for (std::size_t i = 0; i < weightNames.size(); ++i) { + if (weightNames[i] == requestedWeightName) { + //static_cast(i) -> converts i from std::size_t to int since weightIndex is int + weightIndex = static_cast(i); + break; + } + } + } //constructor ends here + + //Functor operator() that will be called for each event + // Functor 2 returns only one weight value for each event, so the return type is double. + + //input type: edm4hep::EventHeaderCollection + //input variable name: eventHeaders + double operator()(const edm4hep::EventHeaderCollection& eventHeaders) const { + //first const → do not modify the input EventHeader collection + //second const → do not modify the GetWeightByName object since operator() only needs to read the saved weightIndex (variable stored in GetWeightByName object), not change it + // If there is no EventHeader for this event, there is no weight to return. + if (eventHeaders.empty()) { + return -1.0; + } + + // If weightIndex is still -1, the requested label was not found. + if (weightIndex < 0) { + return -1.0; + } + //getting the eventheader from the eventheaders collection (one event header per event so index 0) + const auto& eventHeader = eventHeaders.at(0); + //Now we need to extract the weights from the single eventHeader + const auto eventWeights = eventHeader.getWeights(); + // getWeights() is an EDM4hep function that gets all weights stored in this EventHeader. + + + // Convert the valid weightIndex from int to std::size_t because array positions and .size() use std::size_t, then store it in index. It was initially declared as int + const auto index = static_cast(weightIndex); + + + //if the label was found, but the current event does not contain enough numerical weights then return -1.0. + if (index >= eventWeights.size()) { + return -1.0; + } + // Return the numerical weight that corresponds to the requested label. + return eventWeights[index]; + } + + +}; + -#endif +#endif \ No newline at end of file From 6e333a585954e726b3829abfe7086af47a40c64f Mon Sep 17 00:00:00 2001 From: Zeynep Eda Cabukoglu Date: Sun, 19 Jul 2026 16:02:46 +0200 Subject: [PATCH 3/6] Fix optional weight label handling --- .../data_source/event_weights/Definitions.h | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/examples/data_source/event_weights/Definitions.h b/examples/data_source/event_weights/Definitions.h index de89c36d8e2..9e3ec7b5d0a 100644 --- a/examples/data_source/event_weights/Definitions.h +++ b/examples/data_source/event_weights/Definitions.h @@ -160,18 +160,29 @@ struct GetWeightByName { //get the list of labels from metadataFrame auto weightNames = metadataFrame.getParameter>("EventWeightNames"); - // Extract the EventWeightNames parameter from metadataFrame as a vector of strings and store it in weightNames. + // Extract the EventWeightNames parameter from metadataFrame as an optional vector of strings and store it in weightNames. + //PODIO’s getParameter() function returns an optional because the metadata parameter "EventWeightNames" might not exist, + // so auto determines weightnames to have the type std::optional>. - // Loop through all weight names, compare each one with the requested name, and store its index when a match is found. - //std::size_t is an unsigned integer type - for (std::size_t i = 0; i < weightNames.size(); ++i) { - if (weightNames[i] == requestedWeightName) { - //static_cast(i) -> converts i from std::size_t to int since weightIndex is int - weightIndex = static_cast(i); - break; - } + // Loop through all weight names, compare each one with the requested name, and store its index when a match is found. + //std::size_t is an unsigned integer type + + + //we cannot do .size or [i] on an optional so + //first check weight Names has a vector inside + if (!weightNames.has_value()) { + return; + } + //then access the vector inside the optional + for (std::size_t i = 0; i < weightNames->size(); ++i) { + if ((*weightNames)[i] == requestedWeightName) { + weightIndex = static_cast(i); + break; } + } + + } //constructor ends here //Functor operator() that will be called for each event @@ -207,7 +218,7 @@ struct GetWeightByName { return -1.0; } // Return the numerical weight that corresponds to the requested label. - return eventWeights[index]; + return eventWeights[index]; } From 4b4f47b761ab495204d3c45cf4d89ce327573038 Mon Sep 17 00:00:00 2001 From: Zeynep Eda Cabukoglu Date: Sun, 19 Jul 2026 16:48:07 +0200 Subject: [PATCH 4/6] Add named weight selection test --- .../event_weights/analysis_stage1.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/examples/data_source/event_weights/analysis_stage1.py b/examples/data_source/event_weights/analysis_stage1.py index 6108a966a99..a1cc3f03b2c 100644 --- a/examples/data_source/event_weights/analysis_stage1.py +++ b/examples/data_source/event_weights/analysis_stage1.py @@ -3,7 +3,7 @@ ''' from argparse import ArgumentParser - +import ROOT # Mandatory: Analysis class where the user defines the operations on the # dataframe. class Analysis(): @@ -91,7 +91,20 @@ def analyzers(self, dframe): "GetAllWeights{}(EventHeader)" ) - return dframe2 + # Create one Functor 2 object and find the index of rwgt_4 once. + selected_weight_functor = ROOT.GetWeightByName( + "rwgt_4", + self.test_file + ) + + # Functor 2: create a second column containing only rwgt_4. + dframe3 = dframe2.Define( + "selected_weight", + selected_weight_functor, + ["EventHeader"] + ) + + return dframe3 # Pass EventHeader into the GetAllWeights functor and store its returned weights in a new column called event_weights. @@ -99,4 +112,4 @@ def analyzers(self, dframe): # Mandatory: output function, please make sure you return the branch list # as a python list def output(self): - return ["event_weights"] + return ["event_weights", "selected_weight"] From 84f8832b7aec5167cd32784a0720479ca566ada9 Mon Sep 17 00:00:00 2001 From: Zeynep Eda Cabukoglu Date: Thu, 30 Jul 2026 12:07:58 +0200 Subject: [PATCH 5/6] Use DataSource metadata for event weight names --- .../data_source/event_weights/Definitions.h | 172 +++++++++--------- 1 file changed, 85 insertions(+), 87 deletions(-) diff --git a/examples/data_source/event_weights/Definitions.h b/examples/data_source/event_weights/Definitions.h index 9e3ec7b5d0a..73c47bb72b9 100644 --- a/examples/data_source/event_weights/Definitions.h +++ b/examples/data_source/event_weights/Definitions.h @@ -11,8 +11,8 @@ #include #include -#include "podio/Reader.h" #include +#include //input: EventHeader //output: weights array @@ -98,113 +98,110 @@ struct GetAllWeights { // FUNCTOR 2 : GetWeightByName // //Finds a weight by name and returns its value for each event, or -1 if unavailable. -// Finds the weight's index by name once, since the index is the same for all events, then returns that weight's value for each event. +// For each event, uses the weight-name list supplied by the DataSource to find +// the requested label's index, then returns the numerical weight at that index. -// Setup inputs (asked once): +// Setup input supplied once when the functor object is created: // requested weight name: std::string -// input file path: std::string -//// Event input: event header -// edm4hep::EventHeaderCollection +// Inputs supplied by RDataFrame: +// eventHeaders: edm4hep::EventHeaderCollection +// weightNames: std::vector +// supplied through the _EventWeightNames DataSource column // Output: // double value or -1.0 if unavailable -// Create one GetWeightByName object and find the requested label's index once. -> GetWeightByName selectedWeight("rwgt_4", inputFile); -// Then call the same object for each event to return that event's selected weight. -> selectedWeight(EventHeader); - -// evetheader is passed for each event, and the functor returns the weight value for that event. - +// Create one GetWeightByName object and store the requested weight label. +// -> GetWeightByName selectedWeight("rwgt_4"); + //object name: selectedweight + //object type: GetWeightByName + // selected weight's label: "rwgt_4" + // The requested label "rwgt_4" is stored in the selectedWeight object. + // The input file is not opened or accessed by this functor. + // RDataFrame calls the object's operator() using the EventHeader collection + // and the _EventWeightNames metadata column. + // Conceptually, RDataFrame calls: + // selectedWeight(EventHeader, _EventWeightNames); + // + // Here, EventHeader and _EventWeightNames are RDataFrame column names. + // The EventHeader is supplied for each event, while _EventWeightNames supplies + // the labels used to identify the requested numerical weight. struct GetWeightByName { - // create a variable to store the index of the requested label - // -1 means not found, so if the weight is not found, the functor will return -1 for each event. - int weightIndex{-1}; - - //creating a constructor (sets up the object for the struct when it's created) - must be the same name as the struct [ex:GetWeightByName A ("label", input file) -> A is the object] - // We specifically need a constructor because Functor 2 must remember setup information -> requested label and its index - //Without a constructor, the functor would have to search the label list every time operator() runs and operator() is called once for every event - - GetWeightByName( - - //inputs to the constructor: input file to find the labels, and the requested label to find its index - //constructor runs whenever a new GetWeightByName object is created - const std::string& requestedWeightName, - // type of the file is string since inputFile is not the file’s contents. It is the path/name used to locate the file. Then PODIO uses that string path to open the actual EDM4hep ROOT file - const std::string& inputFile) - { - // Create the appropriate PODIO reader for the input file format. - // Reader implementation: - // https://github.com/AIDASoft/podio/blob/master/src/Reader.cc - // - // reader is a PODIO object that opens the input file - // and allows us to read its PODIO frames. - // In this project, those frames contain EDM4hep data, - // including the weight labels we want to find. - auto reader = podio::makeReader(inputFile); - - - // Use the reader for the opened input file, select the "metadata" frame category, - - // and read entry 0, which is the first and only metadata entry and contains the weight labels. + // Store the name of the requested weight. + // For example, requestedWeightName may contain "rwgt_4". + std::string requestedWeightName; + // Constructor: runs when a GetWeightByName object is created. + // It stores the requested label inside the object. + // Example: GetWeightByName selectedWeight("rwgt_4"); + + // The constructor allows the functor object to remember which label was requested. + //The constructor defines what happens to that supplied value (requestedWeightName) + // It stores the label, but it does not open the file or read metadata. + GetWeightByName(const std::string& weightName) + : requestedWeightName(weightName) { + } + // operator() receives the label list from the DataSource and uses it to locate + // the stored requestedWeightName. - auto metadataFrame = reader.readFrame("metadata", 0); - //podio::Frame readFrame(std::string_view name, size_t index, const std::vector& collsToRead = {} - // @param name The category name = "metadata" - // @param index The entry number to read -> podio-dump showed metadata 1 so its index is 0 - // metadataFrame holds the file’s first metadata frame, which contains the EventWeightNames parameter. - - - - //get the list of labels from metadataFrame - auto weightNames = - metadataFrame.getParameter>("EventWeightNames"); - // Extract the EventWeightNames parameter from metadataFrame as an optional vector of strings and store it in weightNames. - //PODIO’s getParameter() function returns an optional because the metadata parameter "EventWeightNames" might not exist, - // so auto determines weightnames to have the type std::optional>. - - - // Loop through all weight names, compare each one with the requested name, and store its index when a match is found. - //std::size_t is an unsigned integer type - + //Functor operator() that will be called for each event + // Functor 2 returns only one weight value for each event, so the return type is double. - //we cannot do .size or [i] on an optional so - //first check weight Names has a vector inside - if (!weightNames.has_value()) { - return; - } - //then access the vector inside the optional - for (std::size_t i = 0; i < weightNames->size(); ++i) { - if ((*weightNames)[i] == requestedWeightName) { - weightIndex = static_cast(i); - break; + // First input: + // type: edm4hep::EventHeaderCollection + // variable name: eventHeaders + // + // Second input: + // type: std::vector + // variable name: weightNames + double operator()( + const edm4hep::EventHeaderCollection& eventHeaders, + const std::vector& weightNames + ) const { + // The const before each input type means operator() may read eventHeaders + // and weightNames, but it may not modify either of them. + // The final const means operator() does not modify the GetWeightByName object. + // It only reads the stored requestedWeightName. + // If there is no EventHeader for this event, there is no weight to return. + if (eventHeaders.empty()) { + return -1.0; } - } + // Create a local variable to store the index of the requested label. + // -1 means that the requested label has not been found. + int weightIndex{-1}; + + // Loop through all weight labels supplied by the DataSource. + // + // std::size_t is the unsigned integer type normally used for + // vector sizes and vector positions. + for (std::size_t i = 0; i < weightNames.size(); ++i) { - } //constructor ends here + // Compare the current label with the label stored in the functor object. + if (weightNames[i] == requestedWeightName) { - //Functor operator() that will be called for each event - // Functor 2 returns only one weight value for each event, so the return type is double. + // Save the matching label's position. + // This same position identifies the corresponding numerical weight + // inside EventHeader.getWeights(). + weightIndex = static_cast(i); - //input type: edm4hep::EventHeaderCollection - //input variable name: eventHeaders - double operator()(const edm4hep::EventHeaderCollection& eventHeaders) const { - //first const → do not modify the input EventHeader collection - //second const → do not modify the GetWeightByName object since operator() only needs to read the saved weightIndex (variable stored in GetWeightByName object), not change it - // If there is no EventHeader for this event, there is no weight to return. - if (eventHeaders.empty()) { - return -1.0; + // The requested label has been found, so the loop can stop. + break; + } } - // If weightIndex is still -1, the requested label was not found. + // If weightIndex is still -1 after the loop, + // the requested label was not present in weightNames. if (weightIndex < 0) { return -1.0; } - //getting the eventheader from the eventheaders collection (one event header per event so index 0) + + // Get the EventHeader from the EventHeader collection. + // There is normally one EventHeader object per event, so its index is 0. const auto& eventHeader = eventHeaders.at(0); - //Now we need to extract the weights from the single eventHeader + // Extract all numerical weights from this event's EventHeader. const auto eventWeights = eventHeader.getWeights(); // getWeights() is an EDM4hep function that gets all weights stored in this EventHeader. @@ -213,11 +210,12 @@ struct GetWeightByName { const auto index = static_cast(weightIndex); - //if the label was found, but the current event does not contain enough numerical weights then return -1.0. + // The label may exist, but the current event may contain fewer numerical + // weights than expected. In that case, the index would be invalid. if (index >= eventWeights.size()) { return -1.0; } - // Return the numerical weight that corresponds to the requested label. + // Return the numerical event weight at the same index as the requested label. return eventWeights[index]; } From edec79eb225191bf43a32baabe62ca594a93dcbb Mon Sep 17 00:00:00 2001 From: Zeynep Eda Cabukoglu Date: Thu, 30 Jul 2026 12:12:54 +0200 Subject: [PATCH 6/6] Use DataSource weight names in event weight analysis --- .../event_weights/analysis_stage1.py | 70 ++++++++----------- 1 file changed, 28 insertions(+), 42 deletions(-) diff --git a/examples/data_source/event_weights/analysis_stage1.py b/examples/data_source/event_weights/analysis_stage1.py index a1cc3f03b2c..8317ec81413 100644 --- a/examples/data_source/event_weights/analysis_stage1.py +++ b/examples/data_source/event_weights/analysis_stage1.py @@ -1,40 +1,20 @@ -''' -Analysis example, measure Higgs mass in the Z(mumu)H recoil measurement. -''' -from argparse import ArgumentParser +'''Example: retrieve event weights from EDM4hep EventHeader.''' + import ROOT # Mandatory: Analysis class where the user defines the operations on the # dataframe. class Analysis(): - ''' - Higgs mass recoil analysis in Z(mumu)H. - ''' + ''' Retrieve per-event weights from the EDM4hep EventHeader: the full weights vector, and one weight selected by name. ''' def __init__(self, cmdline_args): - # Parse additional arguments not known to the FCCAnalyses parsers. - # All command line arguments are provided in the `cmdline_arg` - # dictionary and arguments after "--" are stored under "remaining" key. - parser = ArgumentParser( - description='Additional analysis arguments', - usage='Provided after "--"') - parser.add_argument('--muon-pt', default='10.', type=float, - help='Minimal pT of the mouns.') - self.ana_args, _ = parser.parse_known_args(cmdline_args['remaining']) - # Mandatory: List of datasets used in the analysis + # Note: currently using the first pp-hhh file since couldn't find metadata in 10000 event file self.process_list = { - # Run over the full statistics and save it to one output file named - # /.root - 'p8_ee_ZZ_ecm240': {'fraction': 1.}, - # Run over 50% of the statistics and save output into two files - # named /p8_ee_WW_ecm240/chunk.root - # Number of input files needs to be larger that number of chunks - 'p8_ee_WW_ecm240': {'fraction': 0.5, 'chunks': 2}, - # Run over 20% of the statistics and save output into one file - # named /p8_ee_ZH_ecm240_out_f02.root - 'p8_ee_ZH_ecm240': {'fraction': 0.2, - 'output': 'p8_ee_ZH_ecm240_out_f02'} + "/afs/cern.ch/user/z/zcabukog/event_weights_project/" + "pp_hhh_84TeV_weights_5evt.edm4hep.root": {} } + #'pp_hhh_84TeV_weights_5evt' + # Mandatory: Production tag when running over the centrally produced # samples (this points to the yaml file for getting sample statistics) @@ -44,9 +24,7 @@ def __init__(self, cmdline_args): self.input_dir = '/eos/experiment/fcc/hh/tutorials/' \ 'edm4hep_tutorial_data/' - # Optional: output directory, default is local running directory - self.output_dir = 'outputs/FCCee/higgs/mH-recoil/mumu/' \ - f'stage1_{self.ana_args.muon_pt}' + #podio #line below tells FCCAnalyses to read the ROOT file using podio::DataSource instead of reading the raw ROOT branches directly @@ -80,30 +58,37 @@ def __init__(self, cmdline_args): self.include_paths = ["Definitions.h"] # Optional: test file - self.test_file = '/afs/cern.ch/user/z/zcabukog/event_weights_project/pp_hhh_84TeV_weights_5evt.edm4hep.root' - + self.test_file = ( + "/afs/cern.ch/user/z/zcabukog/event_weights_project/" + "pp_hhh_84TeV_weights_5evt.edm4hep.root" + ) # Mandatory: analyzers function to define the analysis graph, please make - # sure you return the dataframe, in this example it is dframe2 + # sure you return the dataframe def analyzers(self, dframe): #define creates a new column in the dataframe, and the first argument is the name of the new column, and the second argument is the function that will be used to create the new column. + dframe2 = dframe.Define( "event_weights", "GetAllWeights{}(EventHeader)" ) - # Create one Functor 2 object and find the index of rwgt_4 once. - selected_weight_functor = ROOT.GetWeightByName( - "rwgt_4", - self.test_file - ) + # Create one GetWeightByName object and store the requested label. + # The functor no longer receives or opens the input file. + selected_weight_functor = ROOT.GetWeightByName("rwgt_4") - # Functor 2: create a second column containing only rwgt_4. + # Create a new column containing the numerical value of rwgt_4 + # for each event. + # + # RDataFrame passes two columns into the functor: + # EventHeader supplies the numerical event weights. + # _EventWeightNames supplies the corresponding weight labels. dframe3 = dframe2.Define( "selected_weight", selected_weight_functor, - ["EventHeader"] + ["EventHeader", "_EventWeightNames"] ) + # Return the dataframe containing both event_weights and selected_weight. return dframe3 # Pass EventHeader into the GetAllWeights functor and store its returned weights in a new column called event_weights. @@ -112,4 +97,5 @@ def analyzers(self, dframe): # Mandatory: output function, please make sure you return the branch list # as a python list def output(self): - return ["event_weights", "selected_weight"] + return ["event_weights", "selected_weight"] +