From ffd067fcf12c924c1b49ee983b10db0fee9778a6 Mon Sep 17 00:00:00 2001 From: Christopher Dilks Date: Sun, 31 Aug 2025 16:48:44 -0400 Subject: [PATCH 01/26] feat: support for clas12root `clas12reader` - new `Algorithm::CreateBank` to _just_ create a new Iguana bank, to be owned by the caller - algorithm-specific `Run` functions, which operate on lvalue references to banks (compatible with `clas12reader`s raw pointers to `hipo::bank`-derived objects) --- src/iguana/algorithms/Algorithm.cc | 9 +++++++++ src/iguana/algorithms/Algorithm.h | 5 +++++ .../physics/InclusiveKinematics/Algorithm.cc | 14 +++++++++++--- .../physics/InclusiveKinematics/Algorithm.h | 12 ++++++++++-- 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/iguana/algorithms/Algorithm.cc b/src/iguana/algorithms/Algorithm.cc index d5cc8da60..c2a5d769f 100644 --- a/src/iguana/algorithms/Algorithm.cc +++ b/src/iguana/algorithms/Algorithm.cc @@ -251,6 +251,15 @@ namespace iguana { /////////////////////////////////////////////////////////////////////////////// + hipo::bank Algorithm::CreateBank(std::string const& bank_name) const noexcept(false) { + hipo::banklist new_banks; + hipo::banklist::size_type new_bank_idx; + CreateBank(new_banks, new_bank_idx, bank_name); + return new_banks.at(0); + } + + /////////////////////////////////////////////////////////////////////////////// + void Algorithm::ShowBanks(hipo::banklist& banks, std::string_view message, Logger::Level const level) const { if(m_log->GetLevel() <= level) { diff --git a/src/iguana/algorithms/Algorithm.h b/src/iguana/algorithms/Algorithm.h index c84f4f0f6..15e934790 100644 --- a/src/iguana/algorithms/Algorithm.h +++ b/src/iguana/algorithms/Algorithm.h @@ -164,6 +164,11 @@ namespace iguana { hipo::banklist::size_type& bank_idx, std::string const& bank_name) const noexcept(false); + /// Create a new bank. The bank must be defined in `src/iguana/bankdefs/iguana.json`. + /// @param [in] bank_name the new bank name + /// @returns the new bank + hipo::bank CreateBank(std::string const& bank_name) const noexcept(false); + /// Dump all banks in a `hipo::banklist` /// @param banks the banks to show /// @param message if specified, print a header message diff --git a/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.cc b/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.cc index 59a180ddb..cfa054dd3 100644 --- a/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.cc +++ b/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.cc @@ -76,9 +76,17 @@ namespace iguana::physics { void InclusiveKinematics::Run(hipo::banklist& banks) const { - auto& particle_bank = GetBank(banks, b_particle, "REC::Particle"); - auto& config_bank = GetBank(banks, b_config, "RUN::config"); - auto& result_bank = GetBank(banks, b_result, GetClassName()); + Run( + GetBank(banks, b_particle, "REC::Particle"), + GetBank(banks, b_config, "RUN::config"), + GetBank(banks, b_result, GetClassName())); + } + + void InclusiveKinematics::Run( + hipo::bank& particle_bank, + hipo::bank& config_bank, + hipo::bank& result_bank) const + { ShowBank(particle_bank, Logger::Header("INPUT PARTICLES")); auto key = PrepareEvent(config_bank.getInt("run",0)); diff --git a/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h b/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h index 12d2e16b9..18b2d064a 100644 --- a/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h +++ b/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h @@ -10,8 +10,7 @@ namespace iguana::physics { /// @brief_algo Calculate inclusive kinematics quantities /// /// @begin_doc_algo{physics::InclusiveKinematics | Creator} - /// @input_banks{REC::Particle, RUN::config} - /// @output_banks{%physics::InclusiveKinematics} + /// see this algorithm's Run function(s) for the input and output bank names /// @end_doc /// /// @begin_doc_config{physics/InclusiveKinematics} @@ -34,6 +33,15 @@ namespace iguana::physics { void Run(hipo::banklist& banks) const override; void Stop() override; + /// run function + /// @param [in] particle_bank `REC::Particle` + /// @param [in] config_bank `RUN::config` + /// @param [out] result_bank `%physics::InclusiveKinematics` + void Run( + hipo::bank& particle_bank, + hipo::bank& config_bank, + hipo::bank& result_bank) const; + /// @action_function{reload} prepare the event /// @when_to_call{for each event} /// @param runnum the run number From 599c3c1c896eb560e8974dd30edb63179f217155 Mon Sep 17 00:00:00 2001 From: Christopher Dilks Date: Wed, 3 Sep 2025 19:30:25 -0400 Subject: [PATCH 02/26] feat: map creator algorithm to its created bank name --- .../iguana_ex_python_00_run_functions.py | 5 ++++- examples/iguana_ex_cpp_00_run_functions.cc | 5 ++++- src/iguana/algorithms/Algorithm.cc | 6 +++++- src/iguana/algorithms/Algorithm.h | 10 ++++++++-- src/iguana/algorithms/AlgorithmFactory.cc | 18 ++++++++++++++++++ 5 files changed, 39 insertions(+), 5 deletions(-) diff --git a/bind/python/iguana_ex_python_00_run_functions.py b/bind/python/iguana_ex_python_00_run_functions.py index 9700385a3..aa8c595df 100755 --- a/bind/python/iguana_ex_python_00_run_functions.py +++ b/bind/python/iguana_ex_python_00_run_functions.py @@ -55,11 +55,14 @@ # start the algorithms seq.Start(banks) +# get the name of newly created banks (if you don't want to look them up in the documentation) +sector_finder_bank_name = iguana.AlgorithmFactory.GetCreatedBankName('clas12::SectorFinder'); + # get bank index, for each bank we want to use after Iguana algorithms run # NOTE: new banks from creator algorithms are initialized by `Start` b_config = hipo.getBanklistIndex(banks, 'RUN::config') b_particle = hipo.getBanklistIndex(banks, 'REC::Particle') -b_sector = hipo.getBanklistIndex(banks, 'REC::Particle::Sector') # new created bank +b_sector = hipo.getBanklistIndex(banks, sector_finder_bank_name) # new created bank # run the algorithm sequence on each event iEvent = 0 diff --git a/examples/iguana_ex_cpp_00_run_functions.cc b/examples/iguana_ex_cpp_00_run_functions.cc index f5758560d..ef087daac 100644 --- a/examples/iguana_ex_cpp_00_run_functions.cc +++ b/examples/iguana_ex_cpp_00_run_functions.cc @@ -55,11 +55,14 @@ int main(int argc, char** argv) // start the algorithms seq.Start(banks); + // get the name of newly created banks (if you don't want to look them up in the documentation) + auto sector_finder_bank_name = iguana::AlgorithmFactory::GetCreatedBankName("clas12::SectorFinder"); + // get bank index, for each bank we want to use after Iguana algorithms run // NOTE: new banks from creator algorithms are initialized by `Start` auto b_config = hipo::getBanklistIndex(banks, "RUN::config"); auto b_particle = hipo::getBanklistIndex(banks, "REC::Particle"); - auto b_sector = hipo::getBanklistIndex(banks, "REC::Particle::Sector"); // new created bank + auto b_sector = hipo::getBanklistIndex(banks, sector_finder_bank_name); // new created bank // run the algorithm sequence on each event int iEvent = 0; diff --git a/src/iguana/algorithms/Algorithm.cc b/src/iguana/algorithms/Algorithm.cc index c2a5d769f..3eda902ed 100644 --- a/src/iguana/algorithms/Algorithm.cc +++ b/src/iguana/algorithms/Algorithm.cc @@ -254,7 +254,11 @@ namespace iguana { hipo::bank Algorithm::CreateBank(std::string const& bank_name) const noexcept(false) { hipo::banklist new_banks; hipo::banklist::size_type new_bank_idx; - CreateBank(new_banks, new_bank_idx, bank_name); + if(bank_name.empty()) { + CreateBank(new_banks, new_bank_idx, AlgorithmFactory::GetCreatedBankName(m_class_name)); + } else { + CreateBank(new_banks, new_bank_idx, bank_name); + } return new_banks.at(0); } diff --git a/src/iguana/algorithms/Algorithm.h b/src/iguana/algorithms/Algorithm.h index 15e934790..620f0c8b8 100644 --- a/src/iguana/algorithms/Algorithm.h +++ b/src/iguana/algorithms/Algorithm.h @@ -165,9 +165,9 @@ namespace iguana { std::string const& bank_name) const noexcept(false); /// Create a new bank. The bank must be defined in `src/iguana/bankdefs/iguana.json`. - /// @param [in] bank_name the new bank name + /// @param [in] bank_name the new bank name; if empty (default), get the creator algorithm's created bank (this will fail if the algorithm does not create exactly 1 new bank) /// @returns the new bank - hipo::bank CreateBank(std::string const& bank_name) const noexcept(false); + hipo::bank CreateBank(std::string const& bank_name = "") const noexcept(false); /// Dump all banks in a `hipo::banklist` /// @param banks the banks to show @@ -267,6 +267,12 @@ namespace iguana { /// @returns the list of algorithms which create it, if any static std::optional> QueryNewBank(std::string const& bank_name) noexcept; + /// Get the name of a bank created by a creator-type algorithm. Fail if there is not exactly one such bank found; _i.e._, this + /// method cannot be used for a creator-type algorithm which creates more than one bank. + /// @param algo_name the algorithm name + /// @returns the name of the created bank + static std::string GetCreatedBankName(std::string const& algo_name) noexcept(false); + private: /// Association between the algorithm names and their creators diff --git a/src/iguana/algorithms/AlgorithmFactory.cc b/src/iguana/algorithms/AlgorithmFactory.cc index b0ab258b6..26fb21dc6 100644 --- a/src/iguana/algorithms/AlgorithmFactory.cc +++ b/src/iguana/algorithms/AlgorithmFactory.cc @@ -32,4 +32,22 @@ namespace iguana { return it->second; return {}; } + + std::string AlgorithmFactory::GetCreatedBankName(std::string const& algo_name) noexcept(false) + { + std::string bank_name = ""; + for(const auto& [bank_it, creator_algo_names] : s_created_banks) { + for(const auto& creator_algo_name : creator_algo_names) { + if(algo_name == creator_algo_name) { + if(!bank_name.empty()) + throw std::runtime_error(fmt::format("algorithm {:?} creates more than one bank; if you called `CreateBank()`, you need to specify the bank name instead", algo_name)); + bank_name = bank_it; + break; // break loop over `creator_algo_names` + } + } + } + if(bank_name.empty()) + throw std::runtime_error(fmt::format("algorithm {:?} does not create any banks", algo_name)); + return bank_name; + } } From 1cb5cb86acbfe1b870e3806725fea17d30eb4da5 Mon Sep 17 00:00:00 2001 From: Christopher Dilks Date: Thu, 4 Sep 2025 19:00:57 -0400 Subject: [PATCH 03/26] feat: some improvements --- .../iguana_ex_python_00_run_functions.py | 2 +- examples/iguana_ex_cpp_00_run_functions.cc | 4 +- src/iguana/algorithms/Algorithm.cc | 90 +++++++++++++------ src/iguana/algorithms/Algorithm.h | 56 ++++++++---- src/iguana/algorithms/AlgorithmFactory.cc | 45 ++++------ 5 files changed, 123 insertions(+), 74 deletions(-) diff --git a/bind/python/iguana_ex_python_00_run_functions.py b/bind/python/iguana_ex_python_00_run_functions.py index aa8c595df..7050efc79 100755 --- a/bind/python/iguana_ex_python_00_run_functions.py +++ b/bind/python/iguana_ex_python_00_run_functions.py @@ -56,7 +56,7 @@ seq.Start(banks) # get the name of newly created banks (if you don't want to look them up in the documentation) -sector_finder_bank_name = iguana.AlgorithmFactory.GetCreatedBankName('clas12::SectorFinder'); +sector_finder_bank_name = seq.Get("clas12::SectorFinder")->GetCreatedBankName(); # get bank index, for each bank we want to use after Iguana algorithms run # NOTE: new banks from creator algorithms are initialized by `Start` diff --git a/examples/iguana_ex_cpp_00_run_functions.cc b/examples/iguana_ex_cpp_00_run_functions.cc index ef087daac..c409b5aa4 100644 --- a/examples/iguana_ex_cpp_00_run_functions.cc +++ b/examples/iguana_ex_cpp_00_run_functions.cc @@ -55,8 +55,8 @@ int main(int argc, char** argv) // start the algorithms seq.Start(banks); - // get the name of newly created banks (if you don't want to look them up in the documentation) - auto sector_finder_bank_name = iguana::AlgorithmFactory::GetCreatedBankName("clas12::SectorFinder"); + // get the name of newly created banks (or you can just get them from the documentation) + auto sector_finder_bank_name = seq.Get("clas12::SectorFinder")->GetCreatedBankName(); // get bank index, for each bank we want to use after Iguana algorithms run // NOTE: new banks from creator algorithms are initialized by `Start` diff --git a/src/iguana/algorithms/Algorithm.cc b/src/iguana/algorithms/Algorithm.cc index 3eda902ed..acbe3d44d 100644 --- a/src/iguana/algorithms/Algorithm.cc +++ b/src/iguana/algorithms/Algorithm.cc @@ -149,7 +149,7 @@ namespace iguana { return idx; } catch(std::runtime_error const& ex) { m_log->Error("required input bank '{}' not found; cannot `Start` algorithm '{}'", bank_name, m_class_name); - auto creators = AlgorithmFactory::QueryNewBank(bank_name); + auto creators = AlgorithmFactory::GetCreatorAlgorithms(bank_name); if(creators) m_log->Error(" -> this bank is created by algorithm(s) [{}]; please `Start` ONE of them BEFORE this algorithm", fmt::join(creators.value(), ", ")); throw std::runtime_error("cannot cache bank index"); @@ -208,7 +208,7 @@ namespace iguana { } catch(std::out_of_range const& o) { m_log->Error("required input bank '{}' not found; cannot `Run` algorithm '{}'", expected_bank_name, m_class_name); - auto creators = AlgorithmFactory::QueryNewBank(expected_bank_name); + auto creators = AlgorithmFactory::GetCreatorAlgorithms(expected_bank_name); if(creators) m_log->Error(" -> this bank is created by algorithm(s) [{}]; please `Run` ONE of them BEFORE this algorithm", fmt::join(creators.value(), ", ")); } @@ -218,18 +218,60 @@ namespace iguana { /////////////////////////////////////////////////////////////////////////////// - hipo::schema Algorithm::CreateBank( - hipo::banklist& banks, - hipo::banklist::size_type& bank_idx, - std::string const& bank_name) const noexcept(false) + std::vector Algorithm::GetCreatedBankNames() const noexcept(false) + { + auto created_banks = AlgorithmFactory::GetCreatedBanks(m_class_name); + if(created_banks) + return created_banks.value(); + throw std::runtime_error("failed to get created bank names"); + } + + /////////////////////////////////////////////////////////////////////////////// + + std::string Algorithm::GetCreatedBankName() const noexcept(false) + { + auto created_banks = GetCreatedBankNames(); + switch(created_banks.size()) { + case 0: + m_log->Error("algorithm {:?} creates no new banks", m_class_name); + break; + case 1: + return created_banks.at(0); + break; + default: + m_log->Error("algorithm {:?} creates more than one bank; they are: [{}]", m_class_name, fmt::join(created_banks, ", ")); + m_log->Error("- if you called `GetCreatedBank` or `GetCreatedBankSchema`, please specify which bank you want"); + m_log->Error("- if you called `GetCreatedBankName`, call `GetCreatedBankNames` instead"); + break; + } + throw std::runtime_error("failed to get created bank names"); + } + + /////////////////////////////////////////////////////////////////////////////// + + hipo::bank Algorithm::GetCreatedBank(std::string const& bank_name) const noexcept(false) { - // loop over bank definitions - // NOTE: `BANK_DEFS` is generated at build-time using `src/iguana/bankdefs/iguana.json` + return hipo::bank(GetCreatedBankSchema(bank_name)); + } + + /////////////////////////////////////////////////////////////////////////////// + + hipo::schema Algorithm::GetCreatedBankSchema(std::string const& bank_name) const noexcept(false) + { + std::string bank_name_arg = bank_name; // copy, to permit modification + + // if the user did not provide a bank name, get it from the list of banks created by the algorithm; + // this will fail if the algorithm creates more than one bank, in which case, the user must + // specify the bank name explicitly + if(bank_name.empty()) + bank_name_arg = GetCreatedBankName(); + + // loop over bank definitions, `BANK_DEFS`, which is generated at build-time using `src/iguana/bankdefs/iguana.json` for(auto const& bank_def : BANK_DEFS) { - if(bank_def.name == bank_name) { + if(bank_def.name == bank_name_arg) { // make sure the new bank is in REGISTER_IGUANA_ALGORITHM - if(!AlgorithmFactory::QueryNewBank(bank_name)) { - m_log->Error("{:?} creates bank {:?}, which is not registered; new banks must be included in `REGISTER_IGUANA_ALGORITHM` arguments", m_class_name, bank_name); + if(!AlgorithmFactory::GetCreatorAlgorithms(bank_name_arg)) { + m_log->Error("algorithm {:?} creates bank {:?}, which is not registered; new banks must be included in `REGISTER_IGUANA_ALGORITHM` arguments", m_class_name, bank_name_arg); throw std::runtime_error("CreateBank failed"); } // create the schema format string @@ -238,28 +280,26 @@ namespace iguana { schema_def.push_back(entry.name + "/" + entry.type); auto format_string = fmt::format("{}", fmt::join(schema_def, ",")); // create the new bank schema - hipo::schema bank_schema(bank_name.c_str(), bank_def.group, bank_def.item); + hipo::schema bank_schema(bank_name_arg.c_str(), bank_def.group, bank_def.item); bank_schema.parse(format_string); - // create the new bank - banks.push_back({bank_schema}); - bank_idx = GetBankIndex(banks, bank_name); return bank_schema; } } - throw std::runtime_error(fmt::format("bank {:?} not found in 'BankDefs.h'; is this bank defined in src/iguana/bankdefs/iguana.json ?", bank_name)); + + throw std::runtime_error(fmt::format("bank {:?} not found in 'BankDefs.h'; is this bank defined in src/iguana/bankdefs/iguana.json ?", bank_name_arg)); } /////////////////////////////////////////////////////////////////////////////// - hipo::bank Algorithm::CreateBank(std::string const& bank_name) const noexcept(false) { - hipo::banklist new_banks; - hipo::banklist::size_type new_bank_idx; - if(bank_name.empty()) { - CreateBank(new_banks, new_bank_idx, AlgorithmFactory::GetCreatedBankName(m_class_name)); - } else { - CreateBank(new_banks, new_bank_idx, bank_name); - } - return new_banks.at(0); + hipo::schema Algorithm::CreateBank( + hipo::banklist& banks, + hipo::banklist::size_type& bank_idx, + std::string const& bank_name) const noexcept(false) + { + auto bank_schema = GetCreatedBankSchema(bank_name); + banks.emplace_back(bank_schema); + bank_idx = GetBankIndex(banks, bank_name); + return bank_schema; } /////////////////////////////////////////////////////////////////////////////// diff --git a/src/iguana/algorithms/Algorithm.h b/src/iguana/algorithms/Algorithm.h index 620f0c8b8..536d8311f 100644 --- a/src/iguana/algorithms/Algorithm.h +++ b/src/iguana/algorithms/Algorithm.h @@ -136,6 +136,27 @@ namespace iguana { /// @param name the directory name void SetConfigDirectory(std::string const& name); + /// Get the list of created bank names, for creator-type algorithms + /// @see `Algorithm::GetCreatedBankName` for algorithms which create only one bank + /// @returns the list of new bank names + std::vector GetCreatedBankNames() const noexcept(false); + + /// Get the created bank name, for creator-type algorithms which create only one new bank + /// @see `Algorithm::GetCreatedBankNames` for algorithms which create more than one new bank + /// @returns the new bank name + std::string GetCreatedBankName() const noexcept(false); + + /// Get a bank created by a creator-type algorithm. The bank must be defined in `src/iguana/bankdefs/iguana.json`. + /// @param [in] bank_name the created bank name, which is only needed if the algorithm creates more than one bank + /// @returns the new bank + hipo::bank GetCreatedBank(std::string const& bank_name = "") const noexcept(false); + + /// Get a bank schema created by a creator-type algorithm. The bank must be defined in `src/iguana/bankdefs/iguana.json`. + /// @see `Algorithm::GetCreatedBank` + /// @param [in] bank_name the created bank name, which is only needed if the algorithm creates more than one bank + /// @returns the new bank schema + hipo::schema GetCreatedBankSchema(std::string const& bank_name = "") const noexcept(false); + protected: // methods /// Parse YAML configuration files. Sets `m_yaml_config`. @@ -164,11 +185,6 @@ namespace iguana { hipo::banklist::size_type& bank_idx, std::string const& bank_name) const noexcept(false); - /// Create a new bank. The bank must be defined in `src/iguana/bankdefs/iguana.json`. - /// @param [in] bank_name the new bank name; if empty (default), get the creator algorithm's created bank (this will fail if the algorithm does not create exactly 1 new bank) - /// @returns the new bank - hipo::bank CreateBank(std::string const& bank_name = "") const noexcept(false); - /// Dump all banks in a `hipo::banklist` /// @param banks the banks to show /// @param message if specified, print a header message @@ -251,34 +267,36 @@ namespace iguana { AlgorithmFactory() = delete; /// Register an algorithm with a unique name. Algorithms register themselves by calling this function. - /// @param name the name of the algorithm (not equivalent to `Object::m_name`) + /// @param algo_name the name of the algorithm (not equivalent to `Object::m_name`) /// @param creator the creator function /// @param new_banks if this algorithm creates *new* banks, list them here /// @returns true if the algorithm has not yet been registered - static bool Register(std::string const& name, algo_creator_t creator, std::vector const new_banks = {}) noexcept; + static bool Register(std::string const& algo_name, algo_creator_t creator, std::vector const new_banks = {}) noexcept; /// Create an algorithm. Throws an exception if the algorithm cannot be created - /// @param name the name of the algorithm, which was used as an argument in the `AlgorithmFactory::Register` call + /// @param algo_name the name of the algorithm, which was used as an argument in the `AlgorithmFactory::Register` call /// @returns the algorithm instance - static algo_t Create(std::string const& name) noexcept(false); + static algo_t Create(std::string const& algo_name) noexcept(false); - /// Check if a bank is created by an algorithm - /// @param bank_name the name of the bank - /// @returns the list of algorithms which create it, if any - static std::optional> QueryNewBank(std::string const& bank_name) noexcept; + /// Get list of creator-type algorithms which create a particular bank + /// @param bank_name the bank name + /// @returns the list of algorithms which create the bank, if any + static std::optional> GetCreatorAlgorithms(std::string const& bank_name) noexcept; - /// Get the name of a bank created by a creator-type algorithm. Fail if there is not exactly one such bank found; _i.e._, this - /// method cannot be used for a creator-type algorithm which creates more than one bank. + /// Get list of banks which are created by a particular creator-type algorithm /// @param algo_name the algorithm name - /// @returns the name of the created bank - static std::string GetCreatedBankName(std::string const& algo_name) noexcept(false); + /// @returns the list of banks which are created by the algorithm, if any + static std::optional> GetCreatedBanks(std::string const& algo_name) noexcept(false); private: /// Association between the algorithm names and their creators static std::unordered_map s_creators; - /// Association between a created bank and its creator algorithms - static std::unordered_map> s_created_banks; + /// Association from a created bank to the creator-type algorithms that create it + static std::unordered_map> s_bank_to_algos; + + /// Association from a creator-type algorithm to the banks it creates + static std::unordered_map> s_algo_to_banks; }; } diff --git a/src/iguana/algorithms/AlgorithmFactory.cc b/src/iguana/algorithms/AlgorithmFactory.cc index 26fb21dc6..821cd4f50 100644 --- a/src/iguana/algorithms/AlgorithmFactory.cc +++ b/src/iguana/algorithms/AlgorithmFactory.cc @@ -3,51 +3,42 @@ namespace iguana { std::unordered_map AlgorithmFactory::s_creators; - std::unordered_map> AlgorithmFactory::s_created_banks; + std::unordered_map> AlgorithmFactory::s_bank_to_algos; - bool AlgorithmFactory::Register(std::string const& name, algo_creator_t creator, std::vector const new_banks) noexcept + bool AlgorithmFactory::Register(std::string const& algo_name, algo_creator_t creator, std::vector const new_banks) noexcept { - if(auto it = s_creators.find(name); it == s_creators.end()) { - s_creators.insert({name, creator}); + if(auto it = s_creators.find(algo_name); it == s_creators.end()) { + s_creators.insert({algo_name, creator}); + s_algo_to_banks.insert({algo_name, new_banks}); for(auto const& new_bank : new_banks) { - if(auto it = s_created_banks.find(new_bank); it == s_created_banks.end()) - s_created_banks.insert({new_bank, {}}); - s_created_banks.at(new_bank).push_back(name); + if(auto it = s_bank_to_algos.find(new_bank); it == s_bank_to_algos.end()) + s_bank_to_algos.insert({new_bank, {}}); + s_bank_to_algos.at(new_bank).push_back(algo_name); } return true; } return false; } - algo_t AlgorithmFactory::Create(std::string const& name) + algo_t AlgorithmFactory::Create(std::string const& algo_name) { - if(auto it = s_creators.find(name); it != s_creators.end()) + if(auto it = s_creators.find(algo_name); it != s_creators.end()) return it->second(); - throw std::runtime_error(fmt::format("AlgorithmFactory: algorithm with name {:?} does not exist", name)); + throw std::runtime_error(fmt::format("AlgorithmFactory: algorithm with name {:?} does not exist", algo_name)); } - std::optional> AlgorithmFactory::QueryNewBank(std::string const& bank_name) noexcept + std::optional> AlgorithmFactory::GetCreatorAlgorithms(std::string const& bank_name) noexcept { - if(auto it = s_created_banks.find(bank_name); it != s_created_banks.end()) + if(auto it = s_bank_to_algos.find(bank_name); it != s_bank_to_algos.end()) return it->second; return {}; } - std::string AlgorithmFactory::GetCreatedBankName(std::string const& algo_name) noexcept(false) + std::optional> AlgorithmFactory::GetCreatedBanks(std::string const& algo_name) noexcept(false) { - std::string bank_name = ""; - for(const auto& [bank_it, creator_algo_names] : s_created_banks) { - for(const auto& creator_algo_name : creator_algo_names) { - if(algo_name == creator_algo_name) { - if(!bank_name.empty()) - throw std::runtime_error(fmt::format("algorithm {:?} creates more than one bank; if you called `CreateBank()`, you need to specify the bank name instead", algo_name)); - bank_name = bank_it; - break; // break loop over `creator_algo_names` - } - } - } - if(bank_name.empty()) - throw std::runtime_error(fmt::format("algorithm {:?} does not create any banks", algo_name)); - return bank_name; + if(auto it = s_algo_to_banks.find(algo_name); it != s_algo_to_banks.end()) + return it->second; + return {}; } + } From 99101ff358fc53ca034f7d96d40ca1bee6b07320 Mon Sep 17 00:00:00 2001 From: Christopher Dilks Date: Fri, 5 Sep 2025 10:46:24 -0400 Subject: [PATCH 04/26] fix: undefined reference to `iguana::AlgorithmFactory::s_algo_to_banks` --- src/iguana/algorithms/AlgorithmFactory.cc | 1 + src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/iguana/algorithms/AlgorithmFactory.cc b/src/iguana/algorithms/AlgorithmFactory.cc index 821cd4f50..6c8bbb855 100644 --- a/src/iguana/algorithms/AlgorithmFactory.cc +++ b/src/iguana/algorithms/AlgorithmFactory.cc @@ -4,6 +4,7 @@ namespace iguana { std::unordered_map AlgorithmFactory::s_creators; std::unordered_map> AlgorithmFactory::s_bank_to_algos; + std::unordered_map> AlgorithmFactory::s_algo_to_banks; bool AlgorithmFactory::Register(std::string const& algo_name, algo_creator_t creator, std::vector const new_banks) noexcept { diff --git a/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h b/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h index 18b2d064a..10f3a9049 100644 --- a/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h +++ b/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h @@ -10,7 +10,7 @@ namespace iguana::physics { /// @brief_algo Calculate inclusive kinematics quantities /// /// @begin_doc_algo{physics::InclusiveKinematics | Creator} - /// see this algorithm's Run function(s) for the input and output bank names + /// see this algorithm's Run function(s) for the input and output bank names /// @end_doc /// /// @begin_doc_config{physics/InclusiveKinematics} From aa284fc57e9b1ad5dc586bc125348ad7531546a3 Mon Sep 17 00:00:00 2001 From: Christopher Dilks Date: Fri, 5 Sep 2025 11:46:38 -0400 Subject: [PATCH 05/26] feat: `iguana_ex_cpp_00_run_functions_with_banks` --- .github/workflows/ci.yml | 3 + .../iguana_ex_python_00_run_functions.py | 2 +- examples/iguana_ex_cpp_00_run_functions.cc | 9 +- ...uana_ex_cpp_00_run_functions_with_banks.cc | 126 ++++++++++++++++++ examples/iguana_ex_cpp_01_action_functions.cc | 1 + examples/meson.build | 1 + 6 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 examples/iguana_ex_cpp_00_run_functions_with_banks.cc diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 43e008352..5d000da15 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -232,6 +232,9 @@ jobs: - name: test example iguana_ex_cpp_00_run_functions.cc run: stdbuf -o0 iguana_ex_cpp_00_run_functions test_data.hipo ${{ env.num_events }} | tee equivalence.00.cpp.txt if: ${{ matrix.id == 'cpp' || matrix.id == 'python' }} + - name: test example iguana_ex_cpp_00_run_functions_with_banks.cc + run: stdbuf -o0 iguana_ex_cpp_00_run_functions_with_banks test_data.hipo ${{ env.num_events }} + if: ${{ matrix.id == 'cpp' || matrix.id == 'python' }} - name: test example iguana_ex_cpp_01_action_functions.cc run: stdbuf -o0 iguana_ex_cpp_01_action_functions test_data.hipo ${{ env.num_events }} | tee equivalence.01.cpp.txt if: ${{ matrix.id == 'cpp' || matrix.id == 'python' }} diff --git a/bind/python/iguana_ex_python_00_run_functions.py b/bind/python/iguana_ex_python_00_run_functions.py index 7050efc79..83e944196 100755 --- a/bind/python/iguana_ex_python_00_run_functions.py +++ b/bind/python/iguana_ex_python_00_run_functions.py @@ -56,7 +56,7 @@ seq.Start(banks) # get the name of newly created banks (if you don't want to look them up in the documentation) -sector_finder_bank_name = seq.Get("clas12::SectorFinder")->GetCreatedBankName(); +sector_finder_bank_name = seq.Get("clas12::SectorFinder").GetCreatedBankName(); # get bank index, for each bank we want to use after Iguana algorithms run # NOTE: new banks from creator algorithms are initialized by `Start` diff --git a/examples/iguana_ex_cpp_00_run_functions.cc b/examples/iguana_ex_cpp_00_run_functions.cc index c409b5aa4..4cc6c72ba 100644 --- a/examples/iguana_ex_cpp_00_run_functions.cc +++ b/examples/iguana_ex_cpp_00_run_functions.cc @@ -1,7 +1,12 @@ /// @begin_doc_example{cpp} /// @file iguana_ex_cpp_00_run_functions.cc -/// @brief Example using **full HIPO banks** with Iguana algorithms' `Run` functions. This example requires the -/// user to have the C++ `hipo::bank` objects; see other examples if you do not have banks in this format. +/// @brief Example using **full HIPO banks** with Iguana algorithms' `Run` functions, using `hipo::banklist` +/// +/// This example requires the user to have the C++ `hipo::banklist` objects, which are lists of `hipo::bank` objects. +/// +/// - see `iguana_ex_cpp_00_run_functions_with_banks.cc` if you prefer just `hipo::bank` objects, rather than `hipo::banklist` +/// - see other examples if you do not have `hipo::bank` objects +/// /// @par Usage /// ```bash /// iguana_ex_cpp_00_run_functions [HIPO_FILE] [NUM_EVENTS] diff --git a/examples/iguana_ex_cpp_00_run_functions_with_banks.cc b/examples/iguana_ex_cpp_00_run_functions_with_banks.cc new file mode 100644 index 000000000..dcc32c11f --- /dev/null +++ b/examples/iguana_ex_cpp_00_run_functions_with_banks.cc @@ -0,0 +1,126 @@ +/// @begin_doc_example{cpp} +/// @file iguana_ex_cpp_00_run_functions_with_banks.cc +/// @brief Example using **full HIPO banks** with Iguana algorithms' `Run` functions, using `hipo::bank` +/// +/// This example requires the user to have the C++ `hipo::bank` objects. +/// +/// - see `iguana_ex_cpp_00_run_functions.cc` if you prefer `hipo::banklist` +/// - see other examples if you do not have `hipo::bank` objects +/// +/// @par Usage +/// ```bash +/// iguana_ex_cpp_00_run_functions_with_banks [HIPO_FILE] [NUM_EVENTS] +/// +/// HIPO_FILE the HIPO file to analyze +/// +/// NUM_EVENTS the number of events to analyze; +/// set to zero to analyze all events +/// ``` +/// @end_doc_example + +#include +#include +#include +#include + +/// main function +int main(int argc, char** argv) +{ + + // parse arguments + char const* inFileName = argc > 1 ? argv[1] : "data.hipo"; + int const numEvents = argc > 2 ? std::stoi(argv[2]) : 3; + + // read input file + hipo::reader reader(inFileName, {0}); + + // set list of banks to be read + hipo::dictionary dict; + reader.readDictionary(dict); + hipo::bank bank_config(dict.getSchema("RUN::config")); + hipo::bank bank_particle(dict.getSchema("RUN::Particle")); + hipo::bank bank_calorimeter(dict.getSchema("REC::Calorimeter")); + hipo::bank bank_track(dict.getSchema("REC::Track")); + hipo::bank bank_scintillator(dict.getSchema("REC::Scintillator")); + + // iguana algorithm sequence + // NOTE: unlike `iguana_ex_cpp_00_run_functions`, we do not use `AlgorithmSequence`, since + // we'll be calling each algorithm's `Run(hipo::bank& bank1, ...)` functions, which are unique + // for each algorithm (unlike `Run(hipo::banklist&)` + iguana::clas12::EventBuilderFilter algo_eventbuilder_filter; // filter by Event Builder PID (a filter algorithm) + iguana::clas12::SectorFinder algo_sector_finder; // get the sector for each particle (a creator algorithm) + iguana::clas12::MomentumCorrection algo_momentum_correction; // momentum corrections (a transformer algorithm) + + // set log levels + // NOTE: this can also be done in a config file + algo_eventbuilder_filter.SetOption("log", "info"); + algo_sector_finder.SetOption("log", "info"); + algo_momentum_correction.SetOption("log", "info"); + + // set algorithm options + // NOTE: this can also be done in a config file, but setting options here OVERRIDES config file settings + algo_eventbuilder_filter.SetOption>("pids", {11, 211, -211}); + + // start the algorithms + algo_eventbuilder_filter.Start(); + algo_sector_finder.Start(); + algo_momentum_correction.Start(); + + // define newly created bank object + hipo::bank bank_sector = algo_sector_finder.GetCreatedBank(); + + // run the algorithm sequence on each event + int iEvent = 0; + hipo::event event; + while(reader.next() && (numEvents == 0 || iEvent++ < numEvents)) { + + // read the event's banks + reader.read(event); + event.getStructure(bank_config); + event.getStructure(bank_particle); + event.getStructure(bank_calorimeter); + event.getStructure(bank_track); + event.getStructure(bank_scintillator); + + // print the event number + fmt::println("===== EVENT {} =====", bank_config.getInt("event", 0)); + + // print the particle bank before Iguana algorithms + fmt::println("----- BEFORE IGUANA -----"); + bank_particle.show(); // the original particle bank + + // run the sequence of Iguana algorithms, in your preferred order + algo_eventbuilder_filter.Run(bank_particle); + algo_sector_finder.Run(bank_particle, bank_track, bank_calorimeter, bank_scintillator, bank_sector); + algo_momentum_correction.Run(bank_particle, bank_sector, bank_config); + + // print the banks after Iguana algorithms + fmt::println("----- AFTER IGUANA -----"); + bank_particle.show(); // the filtered particle bank, with corrected momenta + bank_sector.show(); // the new sector bank + + // print a table; first the header + fmt::print("----- Analysis Particles -----\n"); + fmt::print(" {:<20} {:<20} {:<20} {:<20}\n", "row == pindex", "PDG", "|p|", "sector"); + // then print a row for each particle + // - use the `hipo::bank::getRowList()` method to loop over the bank rows that PASS the filter + // - if you'd rather loop over ALL bank rows, iterate from `i=0` up to `i < hipo::bank::getRows()` instead + for(auto const& row : bank_particle.getRowList()) { + auto p = std::hypot( + bank_particle.getFloat("px", row), + bank_particle.getFloat("py", row), + bank_particle.getFloat("pz", row)); + auto pdg = bank_particle.getInt("pid", row); + auto sector = bank_sector.getInt("sector", row); + fmt::print(" {:<20} {:<20} {:<20.3f} {:<20}\n", row, pdg, p, sector); + } + fmt::print("\n"); + + } + + // stop algorithms + algo_eventbuilder_filter.Stop(); + algo_sector_finder.Stop(); + algo_momentum_correction.Stop(); + return 0; +} diff --git a/examples/iguana_ex_cpp_01_action_functions.cc b/examples/iguana_ex_cpp_01_action_functions.cc index 568ff7dcd..66ee1c3a7 100644 --- a/examples/iguana_ex_cpp_01_action_functions.cc +++ b/examples/iguana_ex_cpp_01_action_functions.cc @@ -151,6 +151,7 @@ int main(int argc, char** argv) // stop the algorithms algo_eventbuilder_filter.Stop(); + algo_sector_finder.Stop(); algo_momentum_correction.Stop(); return 0; } diff --git a/examples/meson.build b/examples/meson.build index d3614dd60..b564c3b62 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -5,6 +5,7 @@ install_subdir('config', install_dir: example_config_files_prefix, strip_directo # example source information example_sources = { 'iguana_ex_cpp_00_run_functions': {'sources': [ 'iguana_ex_cpp_00_run_functions.cc' ]}, + 'iguana_ex_cpp_00_run_functions_with_banks': {'sources': [ 'iguana_ex_cpp_00_run_functions_with_banks.cc' ]}, 'iguana_ex_cpp_01_action_functions': {'sources': [ 'iguana_ex_cpp_01_action_functions.cc' ]}, 'iguana_ex_cpp_dataframes': { 'sources': [ 'iguana_ex_cpp_dataframes.cc' ], From 649b8cda76a71e2f7bf8ffa2b10fcbb78897c6dc Mon Sep 17 00:00:00 2001 From: Christopher Dilks Date: Fri, 5 Sep 2025 18:56:02 -0400 Subject: [PATCH 06/26] feat: more `Run` functions --- src/iguana/algorithms/Algorithm.cc | 4 +- src/iguana/algorithms/Algorithm.h | 4 +- .../clas12/EventBuilderFilter/Algorithm.cc | 7 +- .../clas12/EventBuilderFilter/Algorithm.h | 7 +- .../clas12/MomentumCorrection/Algorithm.cc | 15 ++- .../clas12/MomentumCorrection/Algorithm.h | 10 ++ .../clas12/SectorFinder/Algorithm.cc | 115 +++++++++++------- .../clas12/SectorFinder/Algorithm.h | 102 ++++++++++++++-- .../clas12/SectorFinder/Config.yaml | 8 +- .../physics/InclusiveKinematics/Algorithm.cc | 4 +- .../physics/InclusiveKinematics/Algorithm.h | 4 +- 11 files changed, 206 insertions(+), 74 deletions(-) diff --git a/src/iguana/algorithms/Algorithm.cc b/src/iguana/algorithms/Algorithm.cc index acbe3d44d..cf6c5f99b 100644 --- a/src/iguana/algorithms/Algorithm.cc +++ b/src/iguana/algorithms/Algorithm.cc @@ -304,7 +304,7 @@ namespace iguana { /////////////////////////////////////////////////////////////////////////////// - void Algorithm::ShowBanks(hipo::banklist& banks, std::string_view message, Logger::Level const level) const + void Algorithm::ShowBanks(hipo::banklist const& banks, std::string_view message, Logger::Level const level) const { if(m_log->GetLevel() <= level) { if(!message.empty()) @@ -316,7 +316,7 @@ namespace iguana { /////////////////////////////////////////////////////////////////////////////// - void Algorithm::ShowBank(hipo::bank& bank, std::string_view message, Logger::Level const level) const + void Algorithm::ShowBank(hipo::bank const& bank, std::string_view message, Logger::Level const level) const { if(m_log->GetLevel() <= level) { if(!message.empty()) diff --git a/src/iguana/algorithms/Algorithm.h b/src/iguana/algorithms/Algorithm.h index 536d8311f..319a5f74a 100644 --- a/src/iguana/algorithms/Algorithm.h +++ b/src/iguana/algorithms/Algorithm.h @@ -189,13 +189,13 @@ namespace iguana { /// @param banks the banks to show /// @param message if specified, print a header message /// @param level the log level - void ShowBanks(hipo::banklist& banks, std::string_view message = "", Logger::Level const level = Logger::trace) const; + void ShowBanks(hipo::banklist const& banks, std::string_view message = "", Logger::Level const level = Logger::trace) const; /// Dump a single bank /// @param bank the bank to show /// @param message if specified, print a header message /// @param level the log level - void ShowBank(hipo::bank& bank, std::string_view message = "", Logger::Level const level = Logger::trace) const; + void ShowBank(hipo::bank const& bank, std::string_view message = "", Logger::Level const level = Logger::trace) const; /// Get an option from the option cache /// @param key the key name associated with this option diff --git a/src/iguana/algorithms/clas12/EventBuilderFilter/Algorithm.cc b/src/iguana/algorithms/clas12/EventBuilderFilter/Algorithm.cc index 7d4fa5b84..ead70ae4c 100644 --- a/src/iguana/algorithms/clas12/EventBuilderFilter/Algorithm.cc +++ b/src/iguana/algorithms/clas12/EventBuilderFilter/Algorithm.cc @@ -18,10 +18,11 @@ namespace iguana::clas12 { void EventBuilderFilter::Run(hipo::banklist& banks) const { + Run(GetBank(banks, b_particle, "REC::Particle")); + } - // get the banks - auto& particleBank = GetBank(banks, b_particle, "REC::Particle"); - + void EventBuilderFilter::Run(hipo::bank& particleBank) const + { // dump the bank ShowBank(particleBank, Logger::Header("INPUT PARTICLES")); diff --git a/src/iguana/algorithms/clas12/EventBuilderFilter/Algorithm.h b/src/iguana/algorithms/clas12/EventBuilderFilter/Algorithm.h index 954b52538..6ef1dd4a2 100644 --- a/src/iguana/algorithms/clas12/EventBuilderFilter/Algorithm.h +++ b/src/iguana/algorithms/clas12/EventBuilderFilter/Algorithm.h @@ -7,8 +7,7 @@ namespace iguana::clas12 { /// @brief_algo Filter the `REC::Particle` (or similar) bank by PID from the Event Builder /// /// @begin_doc_algo{clas12::EventBuilderFilter | Filter} - /// @input_banks{REC::Particle} - /// @output_banks{REC::Particle} + /// see this algorithm's Run function(s) for the input and output bank names /// @end_doc /// /// @begin_doc_config{clas12/EventBuilderFilter} @@ -25,6 +24,10 @@ namespace iguana::clas12 { void Run(hipo::banklist& banks) const override; void Stop() override; + /// run function + /// @param [in,out] particleBank `REC::Particle`, which will be filtered + void Run(hipo::bank& particleBank) const; + /// @action_function{scalar filter} checks if the PDG `pid` is a part of the list of user-specified PDGs /// @param pid the particle PDG to check /// @returns `true` if `pid` is one the user wants diff --git a/src/iguana/algorithms/clas12/MomentumCorrection/Algorithm.cc b/src/iguana/algorithms/clas12/MomentumCorrection/Algorithm.cc index 233eb6cfa..3e42f3a59 100644 --- a/src/iguana/algorithms/clas12/MomentumCorrection/Algorithm.cc +++ b/src/iguana/algorithms/clas12/MomentumCorrection/Algorithm.cc @@ -15,9 +15,18 @@ namespace iguana::clas12 { void MomentumCorrection::Run(hipo::banklist& banks) const { - auto& particleBank = GetBank(banks, b_particle, "REC::Particle"); - auto& sectorBank = GetBank(banks, b_sector, "REC::Particle::Sector"); - auto& configBank = GetBank(banks, b_config, "RUN::config"); + Run( + GetBank(banks, b_particle, "REC::Particle"), + GetBank(banks, b_sector, "REC::Particle::Sector"), + GetBank(banks, b_config, "RUN::config")); + } + + + void MomentumCorrection::Run( + hipo::bank& particleBank, + hipo::bank const& sectorBank, + hipo::bank const& configBank) const + { ShowBank(particleBank, Logger::Header("INPUT PARTICLES")); auto torus = configBank.getFloat("torus", 0); diff --git a/src/iguana/algorithms/clas12/MomentumCorrection/Algorithm.h b/src/iguana/algorithms/clas12/MomentumCorrection/Algorithm.h index 38fa4f343..d19815a03 100644 --- a/src/iguana/algorithms/clas12/MomentumCorrection/Algorithm.h +++ b/src/iguana/algorithms/clas12/MomentumCorrection/Algorithm.h @@ -10,6 +10,7 @@ namespace iguana::clas12 { /// Adapted from /// /// @begin_doc_algo{clas12::MomentumCorrection | Transformer} + /// see this algorithm's Run function(s) for the input and output bank names /// @input_banks{RUN::config, REC::Particle, REC::Particle::Sector} /// @output_banks{REC::Particle} /// @end_doc @@ -24,6 +25,15 @@ namespace iguana::clas12 { void Run(hipo::banklist& banks) const override; void Stop() override; + /// run function + /// @param [in,out] particleBank `REC::Particle`; the momenta will be corrected + /// @param [in] sectorBank `REC::Particle::Sector`, from `SectorFinder` + /// @param [in] configBank `RUN::config` + void Run( + hipo::bank& particleBank, + hipo::bank const& sectorBank, + hipo::bank const& configBank) const; + /// @action_function{scalar transformer} Apply the momentum correction /// @param px @f$p_x@f$ /// @param py @f$p_y@f$ diff --git a/src/iguana/algorithms/clas12/SectorFinder/Algorithm.cc b/src/iguana/algorithms/clas12/SectorFinder/Algorithm.cc index 070827eff..c119a552d 100644 --- a/src/iguana/algorithms/clas12/SectorFinder/Algorithm.cc +++ b/src/iguana/algorithms/clas12/SectorFinder/Algorithm.cc @@ -10,7 +10,13 @@ namespace iguana::clas12 { // define options, their default values, and cache them ParseYAMLConfig(); o_bankname_charged = GetOptionScalar("bank_charged"); - o_bankname_uncharged = GetOptionScalar("bank_uncharged"); + try { + o_bankname_neutral = GetOptionScalar("bank_neutral"); + } catch(std::runtime_error const& ex) { + m_log->Warn("searching instead for configuration parameter named 'bank_uncharged'..."); + o_bankname_neutral = GetOptionScalar("bank_uncharged"); + m_log->Warn("...found 'bank_uncharged' and using it; note that 'bank_uncharged' has been renamed to 'bank_neutral', please update your configuration"); + } bool setDefaultBanks=false; // get expected bank indices @@ -27,9 +33,9 @@ namespace iguana::clas12 { userSpecifiedBank_charged = false; } - if(o_bankname_uncharged != "default") { - b_user_uncharged = GetBankIndex(banks, o_bankname_uncharged); - userSpecifiedBank_uncharged = true; + if(o_bankname_neutral != "default") { + b_user_neutral = GetBankIndex(banks, o_bankname_neutral); + userSpecifiedBank_neutral = true; } else { //avoid setting default banks twice @@ -39,7 +45,7 @@ namespace iguana::clas12 { b_scint = GetBankIndex(banks, "REC::Scintillator"); setDefaultBanks = true; } - userSpecifiedBank_uncharged = false; + userSpecifiedBank_neutral = false; } // create the output bank @@ -50,66 +56,83 @@ namespace iguana::clas12 { void SectorFinder::Run(hipo::banklist& banks) const { - auto& particleBank = GetBank(banks, b_particle, "REC::Particle"); - auto& resultBank = GetBank(banks, b_result, "REC::Particle::Sector"); - - std::vector sectors_user_uncharged; - std::vector pindices_user_uncharged; - if(userSpecifiedBank_uncharged){ - auto const& userBank = GetBank(banks, b_user_uncharged); - GetListsSectorPindex(userBank,sectors_user_uncharged,pindices_user_uncharged); - } + auto includeDefaultBanks = !(userSpecifiedBank_charged && userSpecifiedBank_neutral); + RunImpl( + &GetBank(banks, b_particle, "REC::Particle"), + includeDefaultBanks ? &GetBank(banks, b_track, "REC::Track") : nullptr, + includeDefaultBanks ? &GetBank(banks, b_calorimeter, "REC::Calorimeter") : nullptr, + includeDefaultBanks ? &GetBank(banks, b_scint, "REC::Scintillator") : nullptr, + userSpecifiedBank_charged ? &GetBank(banks, b_user_charged) : nullptr, + userSpecifiedBank_neutral ? &GetBank(banks, b_user_neutral) : nullptr, + &GetBank(banks, b_result, "REC::Particle::Sector")); + } - std::vector sectors_user_charged; - std::vector pindices_user_charged; - if(userSpecifiedBank_charged){ - auto const& userBank = GetBank(banks, b_user_charged); - GetListsSectorPindex(userBank,sectors_user_charged,pindices_user_charged); - } + void SectorFinder::RunImpl( + hipo::bank const* particleBank, + hipo::bank const* trackBank, + hipo::bank const* calBank, + hipo::bank const* scintBank, + hipo::bank const* userChargedBank, + hipo::bank const* userNeutralBank, + hipo::bank* resultBank) const + { std::vector sectors_track; std::vector pindices_track; - if(!userSpecifiedBank_charged || !userSpecifiedBank_uncharged){ - auto const& trackBank = GetBank(banks, b_track); - GetListsSectorPindex(trackBank,sectors_track,pindices_track); - } - std::vector sectors_cal; std::vector pindices_cal; - if(!userSpecifiedBank_charged || !userSpecifiedBank_uncharged){ - auto const& calBank = GetBank(banks, b_calorimeter); - GetListsSectorPindex(calBank,sectors_cal,pindices_cal); - } - std::vector sectors_scint; std::vector pindices_scint; - if(!userSpecifiedBank_charged || !userSpecifiedBank_uncharged){ - auto const& scintBank = GetBank(banks, b_scint); - GetListsSectorPindex(scintBank,sectors_scint,pindices_scint); + std::vector sectors_user_neutral; + std::vector pindices_user_neutral; + std::vector sectors_user_charged; + std::vector pindices_user_charged; + + if(!userSpecifiedBank_charged || !userSpecifiedBank_neutral){ + if(trackBank!=nullptr && calBank!=nullptr && scintBank!=nullptr) { + GetListsSectorPindex(*trackBank,sectors_track,pindices_track); + GetListsSectorPindex(*calBank,sectors_cal,pindices_cal); + GetListsSectorPindex(*scintBank,sectors_scint,pindices_scint); + } + else + throw std::runtime_error("SectorFinder::RunImpl called with unexpected null pointer to either the track, calorimeter, or scintillator bank(s); please contact the maintainers"); + } + + if(userSpecifiedBank_neutral){ + if(userNeutralBank!=nullptr) + GetListsSectorPindex(*userNeutralBank,sectors_user_neutral,pindices_user_neutral); + else + throw std::runtime_error("SectorFinder::RunImpl called with unexpected null pointer to a user-specified bank; please contact the maintainers"); } + if(userSpecifiedBank_charged){ + if(userChargedBank!=nullptr) + GetListsSectorPindex(*userChargedBank,sectors_user_charged,pindices_user_charged); + else + throw std::runtime_error("SectorFinder::RunImpl called with unexpected null pointer to a user-specified bank; please contact the maintainers"); + } // sync new bank with particle bank, and fill it with zeroes - resultBank.setRows(particleBank.getRows()); - resultBank.getMutableRowList().setList(particleBank.getRowList()); - for(int row = 0; row < resultBank.getRows(); row++){ - resultBank.putInt(i_sector, row, 0); - resultBank.putShort(i_pindex, row, static_cast(row)); + resultBank->setRows(particleBank->getRows()); + resultBank->getMutableRowList().setList(particleBank->getRowList()); + for(int row = 0; row < resultBank->getRows(); row++){ + resultBank->putInt(i_sector, row, 0); + resultBank->putShort(i_pindex, row, static_cast(row)); } // some downstream algorithms may still need sector info, so obtain sector for _all_ particles, // not just the ones that were filtered out (use `.getRows()` rather than `.getRowList()`) - for(int row = 0; row < particleBank.getRows(); row++) { + for(int row = 0; row < particleBank->getRows(); row++) { - auto charge=particleBank.getInt("charge",row); + auto charge=particleBank->getInt("charge",row); int sect = -1; // if user-specified bank - if(charge==0 ? userSpecifiedBank_uncharged : userSpecifiedBank_charged) + if(charge==0 ? userSpecifiedBank_neutral : userSpecifiedBank_charged) sect = GetSector( - charge==0 ? sectors_user_uncharged : sectors_user_charged, - charge==0 ? pindices_user_uncharged : pindices_user_charged, + charge==0 ? sectors_user_neutral : sectors_user_charged, + charge==0 ? pindices_user_neutral : pindices_user_charged, row); else // if not user-specified bank, use the standard method sect = GetStandardSector( @@ -122,12 +145,12 @@ namespace iguana::clas12 { row); if (sect!=-1){ - resultBank.putInt(i_sector, row, sect); - resultBank.putShort(i_pindex, row, static_cast(row)); + resultBank->putInt(i_sector, row, sect); + resultBank->putShort(i_pindex, row, static_cast(row)); } } - ShowBank(resultBank, Logger::Header("CREATED BANK")); + ShowBank(*resultBank, Logger::Header("CREATED BANK")); } void SectorFinder::GetListsSectorPindex(hipo::bank const& bank, std::vector& sectors, std::vector& pindices) const diff --git a/src/iguana/algorithms/clas12/SectorFinder/Algorithm.h b/src/iguana/algorithms/clas12/SectorFinder/Algorithm.h index 2a13c2386..0bb2b56aa 100644 --- a/src/iguana/algorithms/clas12/SectorFinder/Algorithm.h +++ b/src/iguana/algorithms/clas12/SectorFinder/Algorithm.h @@ -8,22 +8,21 @@ namespace iguana::clas12 { /// @brief_algo Find the sector for all rows in `REC::Particle` /// /// @begin_doc_algo{clas12::SectorFinder | Creator} - /// @input_banks{REC::Particle, REC::Track, REC::Calorimeter, REC::Scintillator} - /// @output_banks{%REC::Particle::Sector} + /// see this algorithm's Run function(s) for the input and output bank names /// @end_doc /// /// @begin_doc_config{clas12/SectorFinder} /// @config_param{bank_charged | string | if not `default`, use this bank for sector finding of charged particles} - /// @config_param{bank_uncharged | string | if not `default`, use this bank for sector finding of neutral particles} + /// @config_param{bank_neutral | string | if not `default`, use this bank for sector finding of neutral particles} /// @end_doc /// - /// If `bank_charged` and/or `bank_uncharged` is default, then all of the following banks are needed, in addition to `REC::Particle`: + /// If `bank_charged` and/or `bank_neutral` is default, then all of the following banks are needed, in addition to `REC::Particle`: /// /// - `REC::Track` /// - `REC::Calorimeter` /// - `REC::Scintillator` /// - /// Otherwise only the bank(s) specified by `bank_charged` and `bank_uncharged` is/are needed, if both of them are non-default. + /// Otherwise only the bank(s) specified by `bank_charged` and `bank_neutral` is/are needed, if both of them are non-default. /// /// The action function ::GetStandardSector identifies the sector(s) using these banks in a priority order, whereas /// the action function ::GetSector uses a single bank's data. @@ -41,6 +40,76 @@ namespace iguana::clas12 { void Run(hipo::banklist& banks) const override; void Stop() override; + /// @brief run function, using track, calorimeter, and scintillator banks for both charged and neutral particles + /// @see this algorithm contains multiple run functions, for if you prefer to use other banks + /// @param [in] particleBank `REC::Particle` + /// @param [in] trackBank `REC::Track` + /// @param [in] calBank `REC::Calorimeter` + /// @param [in] scintBank `REC::Scintillator` + /// @param [out] resultBank the output `REC::Particle::Sector` bank + void Run( + hipo::bank const& particleBank, + hipo::bank const& trackBank, + hipo::bank const& calBank, + hipo::bank const& scintBank, + hipo::bank& resultBank) const + { + RunImpl(&particleBank, &trackBank, &calBank, &scintBank, nullptr, nullptr, &resultBank); + } + + /// @brief run function, using track, calorimeter, and scintillator banks for charged particles, and a custom bank for neutral particles + /// @see this algorithm contains multiple run functions, for if you prefer to use other banks + /// @param [in] particleBank `REC::Particle` + /// @param [in] trackBank `REC::Track` + /// @param [in] calBank `REC::Calorimeter` + /// @param [in] scintBank `REC::Scintillator` + /// @param [in] userChargedBank custom bank used to obtain charged-particles' sectors + /// @param [out] resultBank the output `REC::Particle::Sector` bank + void RunWithCustomChargedBank( + hipo::bank const& particleBank, + hipo::bank const& trackBank, + hipo::bank const& calBank, + hipo::bank const& scintBank, + hipo::bank const& userChargedBank, + hipo::bank& resultBank) const + { + RunImpl(&particleBank, &trackBank, &calBank, &scintBank, &userChargedBank, nullptr, &resultBank); + } + + /// @brief run function, using track, calorimeter, and scintillator banks for neutral particles, and a custom bank for charged particles + /// @see this algorithm contains multiple run functions, for if you prefer to use other banks + /// @param [in] particleBank `REC::Particle` + /// @param [in] trackBank `REC::Track` + /// @param [in] calBank `REC::Calorimeter` + /// @param [in] scintBank `REC::Scintillator` + /// @param [in] userNeutralBank custom bank used to obtain neutral-particles' sectors + /// @param [out] resultBank the output `REC::Particle::Sector` bank + void RunWithCustomNeutralBank( + hipo::bank const& particleBank, + hipo::bank const& trackBank, + hipo::bank const& calBank, + hipo::bank const& scintBank, + hipo::bank const& userNeutralBank, + hipo::bank& resultBank) const + { + RunImpl(&particleBank, &trackBank, &calBank, &scintBank, nullptr, &userNeutralBank, &resultBank); + } + + /// @brief run function, using custom banks for both charged and neutral particles + /// @see this algorithm contains multiple run functions, for if you prefer to use other banks + /// @param [in] particleBank `REC::Particle` + /// @param [in] userChargedBank custom bank used to obtain charged-particles' sectors + /// @param [in] userNeutralBank custom bank used to obtain neutral-particles' sectors + /// @param [out] resultBank the output `REC::Particle::Sector` bank + void RunWithCustomBanks( + hipo::bank const& particleBank, + hipo::bank const& userChargedBank, + hipo::bank const& userNeutralBank, + hipo::bank& resultBank) const + { + RunImpl(&particleBank, nullptr, nullptr, nullptr, &userChargedBank, &userNeutralBank, &resultBank); + } + /// @action_function{scalar creator} for a given particle with index `pindex_particle`, get its sector from /// a detector bank's list of `sectors` and `pindices` (both must be ordered in the same way) /// @@ -150,16 +219,33 @@ namespace iguana::clas12 { private: + /// @brief private implementation of the run function, called by public run functions + /// @param [in] particleBank `REC::Particle` + /// @param [in] trackBank `REC::Track` + /// @param [in] calBank `REC::Calorimeter` + /// @param [in] scintBank `REC::Scintillator` + /// @param [in] userChargedBank custom bank used to obtain charged-particles' sectors + /// @param [in] userNeutralBank custom bank used to obtain neutral-particles' sectors + /// @param [out] resultBank the output `REC::Particle::Sector` bank + void RunImpl( + hipo::bank const* particleBank, + hipo::bank const* trackBank, + hipo::bank const* calBank, + hipo::bank const* scintBank, + hipo::bank const* userChargedBank, + hipo::bank const* userNeutralBank, + hipo::bank* resultBank) const; + /// `hipo::banklist` index for the particle bank hipo::banklist::size_type b_particle; hipo::banklist::size_type b_calorimeter; hipo::banklist::size_type b_track; hipo::banklist::size_type b_scint; hipo::banklist::size_type b_user_charged; - hipo::banklist::size_type b_user_uncharged; + hipo::banklist::size_type b_user_neutral; hipo::banklist::size_type b_result; bool userSpecifiedBank_charged{false}; - bool userSpecifiedBank_uncharged{true}; + bool userSpecifiedBank_neutral{false}; // `b_result` bank item indices int i_sector; @@ -167,7 +253,7 @@ namespace iguana::clas12 { /// Configuration options std::string o_bankname_charged; - std::string o_bankname_uncharged; + std::string o_bankname_neutral; //only want sectors from FD detectors std::set const listFDDets{ diff --git a/src/iguana/algorithms/clas12/SectorFinder/Config.yaml b/src/iguana/algorithms/clas12/SectorFinder/Config.yaml index be33a1eec..9cb2cc6b7 100644 --- a/src/iguana/algorithms/clas12/SectorFinder/Config.yaml +++ b/src/iguana/algorithms/clas12/SectorFinder/Config.yaml @@ -1,9 +1,9 @@ clas12::SectorFinder: - ### use the default banks for both charged/uncharged particles + ### use the default banks for both charged/neutral particles bank_charged: default - bank_uncharged: default + bank_neutral: default ### alternatively, use custom banks; for example: - # bank_charged: default # default bank for charged particles - # bank_uncharged: REC::Calorimeter # custom bank for neutral particles + # bank_charged: default # default bank for charged particles + # bank_neutral: REC::Calorimeter # custom bank for neutral particles diff --git a/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.cc b/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.cc index cfa054dd3..e0774cfe0 100644 --- a/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.cc +++ b/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.cc @@ -83,8 +83,8 @@ namespace iguana::physics { } void InclusiveKinematics::Run( - hipo::bank& particle_bank, - hipo::bank& config_bank, + hipo::bank const& particle_bank, + hipo::bank const& config_bank, hipo::bank& result_bank) const { ShowBank(particle_bank, Logger::Header("INPUT PARTICLES")); diff --git a/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h b/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h index 10f3a9049..5aec7bd28 100644 --- a/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h +++ b/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h @@ -38,8 +38,8 @@ namespace iguana::physics { /// @param [in] config_bank `RUN::config` /// @param [out] result_bank `%physics::InclusiveKinematics` void Run( - hipo::bank& particle_bank, - hipo::bank& config_bank, + hipo::bank const& particle_bank, + hipo::bank const& config_bank, hipo::bank& result_bank) const; /// @action_function{reload} prepare the event From d31ff3f9c7967169c18427e70f6c44cb89079932 Mon Sep 17 00:00:00 2001 From: Christopher Dilks Date: Fri, 5 Sep 2025 19:01:31 -0400 Subject: [PATCH 07/26] feat: another run function --- .../algorithms/clas12/ZVertexFilter/Algorithm.cc | 10 ++++++---- src/iguana/algorithms/clas12/ZVertexFilter/Algorithm.h | 8 ++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/iguana/algorithms/clas12/ZVertexFilter/Algorithm.cc b/src/iguana/algorithms/clas12/ZVertexFilter/Algorithm.cc index f869c229c..e91a9fd9c 100644 --- a/src/iguana/algorithms/clas12/ZVertexFilter/Algorithm.cc +++ b/src/iguana/algorithms/clas12/ZVertexFilter/Algorithm.cc @@ -20,11 +20,13 @@ namespace iguana::clas12 { void ZVertexFilter::Run(hipo::banklist& banks) const { + Run( + GetBank(banks, b_particle, "REC::Particle"), + GetBank(banks, b_config, "RUN::config")); + } - // get the banks - auto& particleBank = GetBank(banks, b_particle, "REC::Particle"); - auto& configBank = GetBank(banks, b_config, "RUN::config"); - + void ZVertexFilter::Run(hipo::bank& particleBank, hipo::bank const& configBank) const + { // dump the bank ShowBank(particleBank, Logger::Header("INPUT PARTICLES")); diff --git a/src/iguana/algorithms/clas12/ZVertexFilter/Algorithm.h b/src/iguana/algorithms/clas12/ZVertexFilter/Algorithm.h index 917158921..4283c0aba 100644 --- a/src/iguana/algorithms/clas12/ZVertexFilter/Algorithm.h +++ b/src/iguana/algorithms/clas12/ZVertexFilter/Algorithm.h @@ -8,8 +8,7 @@ namespace iguana::clas12 { /// @brief_algo Filter the `REC::Particle` (or similar) bank by cutting on Z Vertex /// /// @begin_doc_algo{clas12::ZVertexFilter | Filter} - /// @input_banks{REC::Particle, RUN::config} - /// @output_banks{REC::Particle} + /// see this algorithm's Run function(s) for the input and output bank names /// @end_doc /// /// @begin_doc_config{clas12/ZVertexFilter} @@ -26,6 +25,11 @@ namespace iguana::clas12 { void Run(hipo::banklist& banks) const override; void Stop() override; + /// run function + /// @param [in,out] particleBank `REC::Particle`, which will be filtered + /// @param [in] configBank `RUN::config` + void Run(hipo::bank& particleBank, hipo::bank const& configBank) const; + /// @action_function{reload} prepare the event /// @when_to_call{for each event} /// @param runnum the run number From 85886bc604b8d66af4bc3ed1d3732ec16b123d35 Mon Sep 17 00:00:00 2001 From: Christopher Dilks Date: Thu, 11 Sep 2025 12:32:42 -0400 Subject: [PATCH 08/26] feat: `Run` functions return `bool` as event-level filter --- doc/gen/Doxyfile.in | 3 ++ src/iguana/algorithms/Algorithm.h | 5 ++- src/iguana/algorithms/AlgorithmSequence.cc | 9 +++-- src/iguana/algorithms/AlgorithmSequence.h | 5 ++- src/iguana/algorithms/Validator.h | 6 +-- .../clas12/CalorimeterLinker/Algorithm.cc | 3 +- .../clas12/CalorimeterLinker/Algorithm.h | 2 +- .../clas12/EventBuilderFilter/Algorithm.cc | 7 ++-- .../clas12/EventBuilderFilter/Algorithm.h | 7 ++-- .../clas12/FTEnergyCorrection/Algorithm.cc | 3 +- .../clas12/FTEnergyCorrection/Algorithm.h | 2 +- .../clas12/FiducialFilter/Algorithm.cc | 3 +- .../clas12/FiducialFilter/Algorithm.h | 2 +- .../clas12/FiducialFilter/Validator.cc | 3 +- .../clas12/FiducialFilter/Validator.h | 2 +- .../clas12/MomentumCorrection/Algorithm.cc | 7 ++-- .../clas12/MomentumCorrection/Algorithm.h | 7 ++-- .../clas12/MomentumCorrection/Validator.cc | 3 +- .../clas12/MomentumCorrection/Validator.h | 2 +- .../clas12/PhotonGBTFilter/Algorithm.cc | 4 +- .../clas12/PhotonGBTFilter/Algorithm.h | 2 +- .../clas12/PhotonGBTFilter/Validator.cc | 3 +- .../clas12/PhotonGBTFilter/Validator.h | 2 +- .../clas12/SectorFinder/Algorithm.cc | 7 ++-- .../clas12/SectorFinder/Algorithm.h | 37 ++++++++++++------- .../clas12/SectorFinder/Validator.cc | 3 +- .../clas12/SectorFinder/Validator.h | 2 +- .../algorithms/clas12/TrajLinker/Algorithm.cc | 3 +- .../algorithms/clas12/TrajLinker/Algorithm.h | 2 +- .../clas12/ZVertexFilter/Algorithm.cc | 7 ++-- .../clas12/ZVertexFilter/Algorithm.h | 7 ++-- .../clas12/ZVertexFilter/Validator.cc | 3 +- .../clas12/ZVertexFilter/Validator.h | 2 +- .../example/ExampleAlgorithm/Algorithm.cc | 7 +++- .../example/ExampleAlgorithm/Algorithm.h | 2 +- .../physics/Depolarization/Algorithm.cc | 3 +- .../physics/Depolarization/Algorithm.h | 2 +- .../physics/Depolarization/Validator.cc | 5 ++- .../physics/Depolarization/Validator.h | 2 +- .../physics/DihadronKinematics/Algorithm.cc | 5 ++- .../physics/DihadronKinematics/Algorithm.h | 2 +- .../physics/DihadronKinematics/Validator.cc | 5 ++- .../physics/DihadronKinematics/Validator.h | 2 +- .../physics/InclusiveKinematics/Algorithm.cc | 9 +++-- .../physics/InclusiveKinematics/Algorithm.h | 8 ++-- .../physics/InclusiveKinematics/Validator.cc | 5 ++- .../physics/InclusiveKinematics/Validator.h | 2 +- .../SingleHadronKinematics/Algorithm.cc | 5 ++- .../SingleHadronKinematics/Algorithm.h | 2 +- .../SingleHadronKinematics/Validator.cc | 5 ++- .../SingleHadronKinematics/Validator.h | 2 +- 51 files changed, 144 insertions(+), 94 deletions(-) diff --git a/doc/gen/Doxyfile.in b/doc/gen/Doxyfile.in index 55b605b3e..a6a71ee38 100644 --- a/doc/gen/Doxyfile.in +++ b/doc/gen/Doxyfile.in @@ -293,6 +293,9 @@ ALIASES += end_doc="" # configuration options ALIASES += begin_doc_config{1}="\par Configuration Options:^^YAML configuration, which includes the default option values:^^@include \1/Config.yaml ^^Table of options and descriptions:^^" ALIASES += config_param{3|}="" +# run functions +ALIASES += run_function="@brief **Run Function**" +ALIASES += run_function_returns_true{}="@returns `true`, _i.e._, this `Run` function does not provide an event-level filter" # action functions ALIASES += action_function{1}="\xrefitem action \"Function Type\" \"List of all Action Functions\" \1 \brief **Action Function:** " ALIASES += when_to_call{1}="@note This function should be called **\1**" diff --git a/src/iguana/algorithms/Algorithm.h b/src/iguana/algorithms/Algorithm.h index 319a5f74a..3d472efa2 100644 --- a/src/iguana/algorithms/Algorithm.h +++ b/src/iguana/algorithms/Algorithm.h @@ -66,7 +66,10 @@ namespace iguana { /// @brief Run this algorithm for an event. /// @param banks the list of banks to process - virtual void Run(hipo::banklist& banks) const = 0; + /// @returns a boolean value, which is typically used to decide whether or not to continue analyzing an event, _i.e._, it can be used + /// as an _event-level_ filter; not all algorithms use or need this feature; see the algorithm's more specialized `Run` functions, + /// which have `hipo::bank` parameters + virtual bool Run(hipo::banklist& banks) const = 0; /// @brief Finalize this algorithm after all events are processed. virtual void Stop() = 0; diff --git a/src/iguana/algorithms/AlgorithmSequence.cc b/src/iguana/algorithms/AlgorithmSequence.cc index a2da03954..c48951e76 100644 --- a/src/iguana/algorithms/AlgorithmSequence.cc +++ b/src/iguana/algorithms/AlgorithmSequence.cc @@ -9,10 +9,13 @@ namespace iguana { for(auto const& algo : m_sequence) algo->Start(banks); } - void AlgorithmSequence::Run(hipo::banklist& banks) const + bool AlgorithmSequence::Run(hipo::banklist& banks) const { - for(auto const& algo : m_sequence) - algo->Run(banks); + for(auto const& algo : m_sequence) { + if(!algo->Run(banks)) + return false; + } + return true; } void AlgorithmSequence::Stop() { diff --git a/src/iguana/algorithms/AlgorithmSequence.h b/src/iguana/algorithms/AlgorithmSequence.h index f2cca6bb3..49397cb09 100644 --- a/src/iguana/algorithms/AlgorithmSequence.h +++ b/src/iguana/algorithms/AlgorithmSequence.h @@ -7,7 +7,8 @@ namespace iguana { /// @brief An algorithm that can run a sequence of algorithms /// /// The `Start`, `Run`, and `Stop` methods will sequentially call the corresponding algorithms' methods, - /// in the order the algorithms were added to the sequence by `AlgorithmSequence::Add`. + /// in the order the algorithms were added to the sequence by `AlgorithmSequence::Add`. If an algorithm's + /// `Run` function returns false, then `AlgorithmSequence`'s `Run` function will stop and return `false`. class AlgorithmSequence : public Algorithm { @@ -16,7 +17,7 @@ namespace iguana { public: void Start(hipo::banklist& banks) override; - void Run(hipo::banklist& banks) const override; + bool Run(hipo::banklist& banks) const override; void Stop() override; /// Create and add an algorithm to the sequence, by name. diff --git a/src/iguana/algorithms/Validator.h b/src/iguana/algorithms/Validator.h index 38bfe1e38..a487bec0a 100644 --- a/src/iguana/algorithms/Validator.h +++ b/src/iguana/algorithms/Validator.h @@ -36,9 +36,9 @@ namespace iguana { } virtual ~Validator() {} - void Start(hipo::banklist& banks) override{}; - void Run(hipo::banklist& banks) const override{}; - void Stop() override{}; + void Start(hipo::banklist& banks) override {} + bool Run(hipo::banklist& banks) const override { return true; } + void Stop() override {} /// Set this validator's output directory /// @param output_dir the output directory diff --git a/src/iguana/algorithms/clas12/CalorimeterLinker/Algorithm.cc b/src/iguana/algorithms/clas12/CalorimeterLinker/Algorithm.cc index cc4079e12..b8c4ad336 100644 --- a/src/iguana/algorithms/clas12/CalorimeterLinker/Algorithm.cc +++ b/src/iguana/algorithms/clas12/CalorimeterLinker/Algorithm.cc @@ -31,7 +31,7 @@ namespace iguana::clas12 { i_ecout_energy = result_schema.getEntryOrder("ecout_energy"); } - void CalorimeterLinker::Run(hipo::banklist& banks) const + bool CalorimeterLinker::Run(hipo::banklist& banks) const { auto& bank_particle = GetBank(banks, b_particle, "REC::Particle"); auto& bank_calorimeter = GetBank(banks, b_calorimeter, "REC::Calorimeter"); @@ -124,6 +124,7 @@ namespace iguana::clas12 { bank_result.putFloat(i_ecout_energy, row_particle, link_particle.ecout_energy); } ShowBank(bank_result, Logger::Header("CREATED BANK")); + return true; } void CalorimeterLinker::Stop() diff --git a/src/iguana/algorithms/clas12/CalorimeterLinker/Algorithm.h b/src/iguana/algorithms/clas12/CalorimeterLinker/Algorithm.h index faa2978ab..b2bbe037f 100644 --- a/src/iguana/algorithms/clas12/CalorimeterLinker/Algorithm.h +++ b/src/iguana/algorithms/clas12/CalorimeterLinker/Algorithm.h @@ -25,7 +25,7 @@ namespace iguana::clas12 { public: void Start(hipo::banklist& banks) override; - void Run(hipo::banklist& banks) const override; + bool Run(hipo::banklist& banks) const override; void Stop() override; private: diff --git a/src/iguana/algorithms/clas12/EventBuilderFilter/Algorithm.cc b/src/iguana/algorithms/clas12/EventBuilderFilter/Algorithm.cc index ead70ae4c..3162beb8b 100644 --- a/src/iguana/algorithms/clas12/EventBuilderFilter/Algorithm.cc +++ b/src/iguana/algorithms/clas12/EventBuilderFilter/Algorithm.cc @@ -16,12 +16,12 @@ namespace iguana::clas12 { } - void EventBuilderFilter::Run(hipo::banklist& banks) const + bool EventBuilderFilter::Run(hipo::banklist& banks) const { - Run(GetBank(banks, b_particle, "REC::Particle")); + return Run(GetBank(banks, b_particle, "REC::Particle")); } - void EventBuilderFilter::Run(hipo::bank& particleBank) const + bool EventBuilderFilter::Run(hipo::bank& particleBank) const { // dump the bank ShowBank(particleBank, Logger::Header("INPUT PARTICLES")); @@ -36,6 +36,7 @@ namespace iguana::clas12 { // dump the modified bank ShowBank(particleBank, Logger::Header("OUTPUT PARTICLES")); + return true; } diff --git a/src/iguana/algorithms/clas12/EventBuilderFilter/Algorithm.h b/src/iguana/algorithms/clas12/EventBuilderFilter/Algorithm.h index 6ef1dd4a2..e6e66fc47 100644 --- a/src/iguana/algorithms/clas12/EventBuilderFilter/Algorithm.h +++ b/src/iguana/algorithms/clas12/EventBuilderFilter/Algorithm.h @@ -21,12 +21,13 @@ namespace iguana::clas12 { public: void Start(hipo::banklist& banks) override; - void Run(hipo::banklist& banks) const override; + bool Run(hipo::banklist& banks) const override; void Stop() override; - /// run function + /// @run_function /// @param [in,out] particleBank `REC::Particle`, which will be filtered - void Run(hipo::bank& particleBank) const; + /// @run_function_returns_true + bool Run(hipo::bank& particleBank) const; /// @action_function{scalar filter} checks if the PDG `pid` is a part of the list of user-specified PDGs /// @param pid the particle PDG to check diff --git a/src/iguana/algorithms/clas12/FTEnergyCorrection/Algorithm.cc b/src/iguana/algorithms/clas12/FTEnergyCorrection/Algorithm.cc index 124c54b9e..c8b881f7d 100644 --- a/src/iguana/algorithms/clas12/FTEnergyCorrection/Algorithm.cc +++ b/src/iguana/algorithms/clas12/FTEnergyCorrection/Algorithm.cc @@ -8,7 +8,7 @@ namespace iguana::clas12 { electron_mass = particle::mass.at(particle::electron); } - void FTEnergyCorrection::Run(hipo::banklist& banks) const { + bool FTEnergyCorrection::Run(hipo::banklist& banks) const { auto& ftParticleBank = GetBank(banks, b_ft_particle, "RECFT::Particle"); ShowBank(ftParticleBank, Logger::Header("INPUT FT PARTICLES")); for(auto const& row : ftParticleBank.getRowList()) { @@ -24,6 +24,7 @@ namespace iguana::clas12 { } } ShowBank(ftParticleBank, Logger::Header("OUTPUT FT PARTICLES")); + return true; } Momentum4 FTEnergyCorrection::Transform( diff --git a/src/iguana/algorithms/clas12/FTEnergyCorrection/Algorithm.h b/src/iguana/algorithms/clas12/FTEnergyCorrection/Algorithm.h index acf82f984..04590da50 100644 --- a/src/iguana/algorithms/clas12/FTEnergyCorrection/Algorithm.h +++ b/src/iguana/algorithms/clas12/FTEnergyCorrection/Algorithm.h @@ -18,7 +18,7 @@ namespace iguana::clas12 { public: void Start(hipo::banklist& banks) override; - void Run(hipo::banklist& banks) const override; + bool Run(hipo::banklist& banks) const override; void Stop() override; /// @action_function{scalar transformer} diff --git a/src/iguana/algorithms/clas12/FiducialFilter/Algorithm.cc b/src/iguana/algorithms/clas12/FiducialFilter/Algorithm.cc index b8214759f..02cb37b26 100644 --- a/src/iguana/algorithms/clas12/FiducialFilter/Algorithm.cc +++ b/src/iguana/algorithms/clas12/FiducialFilter/Algorithm.cc @@ -28,7 +28,7 @@ namespace iguana::clas12 { ////////////////////////////////////////////////////////////////////////////////// - void FiducialFilter::Run(hipo::banklist& banks) const { + bool FiducialFilter::Run(hipo::banklist& banks) const { auto& particleBank = GetBank(banks, b_particle, "REC::Particle"); auto& configBank = GetBank(banks, b_config, "RUN::config"); @@ -74,6 +74,7 @@ namespace iguana::clas12 { } ShowBank(particleBank, Logger::Header("OUTPUT PARTICLES")); + return true; } ////////////////////////////////////////////////////////////////////////////////// diff --git a/src/iguana/algorithms/clas12/FiducialFilter/Algorithm.h b/src/iguana/algorithms/clas12/FiducialFilter/Algorithm.h index ea74f236a..fa7216343 100644 --- a/src/iguana/algorithms/clas12/FiducialFilter/Algorithm.h +++ b/src/iguana/algorithms/clas12/FiducialFilter/Algorithm.h @@ -40,7 +40,7 @@ namespace iguana::clas12 { }; void Start(hipo::banklist& banks) override; - void Run(hipo::banklist& banks) const override; + bool Run(hipo::banklist& banks) const override; void Stop() override; /// @action_function{scalar filter} top-level fiducial cut for RG-A Pass 1 diff --git a/src/iguana/algorithms/clas12/FiducialFilter/Validator.cc b/src/iguana/algorithms/clas12/FiducialFilter/Validator.cc index 9c07d6626..40f3417d6 100644 --- a/src/iguana/algorithms/clas12/FiducialFilter/Validator.cc +++ b/src/iguana/algorithms/clas12/FiducialFilter/Validator.cc @@ -83,7 +83,7 @@ namespace iguana::clas12 { } - void FiducialFilterValidator::Run(hipo::banklist& banks) const + bool FiducialFilterValidator::Run(hipo::banklist& banks) const { auto& particle_bank = GetBank(banks, b_particle, "REC::Particle"); auto& traj_bank = GetBank(banks, b_traj, "REC::Particle::Traj"); @@ -119,6 +119,7 @@ namespace iguana::clas12 { if(traj_bank.getByte("r3_found", row) == 1) u_DC3_after.at(pid)->Fill(traj_bank.getFloat("r3_x", row), traj_bank.getFloat("r3_y", row)); } + return true; } diff --git a/src/iguana/algorithms/clas12/FiducialFilter/Validator.h b/src/iguana/algorithms/clas12/FiducialFilter/Validator.h index 4f04c2252..d24c447a6 100644 --- a/src/iguana/algorithms/clas12/FiducialFilter/Validator.h +++ b/src/iguana/algorithms/clas12/FiducialFilter/Validator.h @@ -22,7 +22,7 @@ namespace iguana::clas12 { public: void Start(hipo::banklist& banks) override; - void Run(hipo::banklist& banks) const override; + bool Run(hipo::banklist& banks) const override; void Stop() override; private: diff --git a/src/iguana/algorithms/clas12/MomentumCorrection/Algorithm.cc b/src/iguana/algorithms/clas12/MomentumCorrection/Algorithm.cc index 3e42f3a59..220cfba3b 100644 --- a/src/iguana/algorithms/clas12/MomentumCorrection/Algorithm.cc +++ b/src/iguana/algorithms/clas12/MomentumCorrection/Algorithm.cc @@ -13,16 +13,16 @@ namespace iguana::clas12 { } - void MomentumCorrection::Run(hipo::banklist& banks) const + bool MomentumCorrection::Run(hipo::banklist& banks) const { - Run( + return Run( GetBank(banks, b_particle, "REC::Particle"), GetBank(banks, b_sector, "REC::Particle::Sector"), GetBank(banks, b_config, "RUN::config")); } - void MomentumCorrection::Run( + bool MomentumCorrection::Run( hipo::bank& particleBank, hipo::bank const& sectorBank, hipo::bank const& configBank) const @@ -46,6 +46,7 @@ namespace iguana::clas12 { } ShowBank(particleBank, Logger::Header("OUTPUT PARTICLES")); + return true; } diff --git a/src/iguana/algorithms/clas12/MomentumCorrection/Algorithm.h b/src/iguana/algorithms/clas12/MomentumCorrection/Algorithm.h index d19815a03..bb13aeba5 100644 --- a/src/iguana/algorithms/clas12/MomentumCorrection/Algorithm.h +++ b/src/iguana/algorithms/clas12/MomentumCorrection/Algorithm.h @@ -22,14 +22,15 @@ namespace iguana::clas12 { public: void Start(hipo::banklist& banks) override; - void Run(hipo::banklist& banks) const override; + bool Run(hipo::banklist& banks) const override; void Stop() override; - /// run function + /// @run_function /// @param [in,out] particleBank `REC::Particle`; the momenta will be corrected /// @param [in] sectorBank `REC::Particle::Sector`, from `SectorFinder` /// @param [in] configBank `RUN::config` - void Run( + /// @run_function_returns_true + bool Run( hipo::bank& particleBank, hipo::bank const& sectorBank, hipo::bank const& configBank) const; diff --git a/src/iguana/algorithms/clas12/MomentumCorrection/Validator.cc b/src/iguana/algorithms/clas12/MomentumCorrection/Validator.cc index f42fee52b..b5bc02e7f 100644 --- a/src/iguana/algorithms/clas12/MomentumCorrection/Validator.cc +++ b/src/iguana/algorithms/clas12/MomentumCorrection/Validator.cc @@ -46,7 +46,7 @@ namespace iguana::clas12 { } - void MomentumCorrectionValidator::Run(hipo::banklist& banks) const + bool MomentumCorrectionValidator::Run(hipo::banklist& banks) const { // get the momenta before auto& particle_bank = GetBank(banks, b_particle, "REC::Particle"); @@ -81,6 +81,7 @@ namespace iguana::clas12 { auto delta_p = p_corrected - p_measured.at(row); u_deltaPvsP.at(pdg).at(sector - 1)->Fill(p_corrected, delta_p); } + return true; } diff --git a/src/iguana/algorithms/clas12/MomentumCorrection/Validator.h b/src/iguana/algorithms/clas12/MomentumCorrection/Validator.h index a552f6c90..16947c1d4 100644 --- a/src/iguana/algorithms/clas12/MomentumCorrection/Validator.h +++ b/src/iguana/algorithms/clas12/MomentumCorrection/Validator.h @@ -18,7 +18,7 @@ namespace iguana::clas12 { public: void Start(hipo::banklist& banks) override; - void Run(hipo::banklist& banks) const override; + bool Run(hipo::banklist& banks) const override; void Stop() override; private: diff --git a/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.cc b/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.cc index e5df2242f..b44b4dfcd 100644 --- a/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.cc +++ b/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.cc @@ -45,7 +45,7 @@ namespace iguana::clas12 { - void PhotonGBTFilter::Run(hipo::banklist& banks) const + bool PhotonGBTFilter::Run(hipo::banklist& banks) const { auto& particleBank = GetBank(banks, b_particle, "REC::Particle"); @@ -70,7 +70,7 @@ namespace iguana::clas12 { // dump the modified bank ShowBank(particleBank, Logger::Header("OUTPUT PARTICLES")); - + return true; } bool PhotonGBTFilter::PidPurityPhotonFilter(float const E, float const Epcal, float const theta) const diff --git a/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.h b/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.h index a4bc6d6ab..dc4db7456 100644 --- a/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.h +++ b/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.h @@ -29,7 +29,7 @@ namespace iguana::clas12 { public: void Start(hipo::banklist& banks) override; - void Run(hipo::banklist& banks) const override; + bool Run(hipo::banklist& banks) const override; void Stop() override; /// Applies forward detector cut using REC::Particle Theta diff --git a/src/iguana/algorithms/clas12/PhotonGBTFilter/Validator.cc b/src/iguana/algorithms/clas12/PhotonGBTFilter/Validator.cc index b668b6286..ce659cd45 100644 --- a/src/iguana/algorithms/clas12/PhotonGBTFilter/Validator.cc +++ b/src/iguana/algorithms/clas12/PhotonGBTFilter/Validator.cc @@ -26,7 +26,7 @@ namespace iguana::clas12 { InitializeHistograms(); } - void PhotonGBTFilterValidator::Run(hipo::banklist& banks) const + bool PhotonGBTFilterValidator::Run(hipo::banklist& banks) const { // get the particle bank auto& particle_bank = GetBank(banks, b_particle, "REC::Particle"); @@ -63,6 +63,7 @@ namespace iguana::clas12 { FillHistograms(photons, 0); FillHistograms(filtered_photons, 1); + return true; } void PhotonGBTFilterValidator::InitializeHistograms() { diff --git a/src/iguana/algorithms/clas12/PhotonGBTFilter/Validator.h b/src/iguana/algorithms/clas12/PhotonGBTFilter/Validator.h index f4b5afc33..39b8791ed 100644 --- a/src/iguana/algorithms/clas12/PhotonGBTFilter/Validator.h +++ b/src/iguana/algorithms/clas12/PhotonGBTFilter/Validator.h @@ -19,7 +19,7 @@ namespace iguana::clas12 { public: void Start(hipo::banklist& banks) override; - void Run(hipo::banklist& banks) const override; + bool Run(hipo::banklist& banks) const override; void Stop() override; private: diff --git a/src/iguana/algorithms/clas12/SectorFinder/Algorithm.cc b/src/iguana/algorithms/clas12/SectorFinder/Algorithm.cc index c119a552d..cdbcdd9de 100644 --- a/src/iguana/algorithms/clas12/SectorFinder/Algorithm.cc +++ b/src/iguana/algorithms/clas12/SectorFinder/Algorithm.cc @@ -54,10 +54,10 @@ namespace iguana::clas12 { i_pindex = result_schema.getEntryOrder("pindex"); } - void SectorFinder::Run(hipo::banklist& banks) const + bool SectorFinder::Run(hipo::banklist& banks) const { auto includeDefaultBanks = !(userSpecifiedBank_charged && userSpecifiedBank_neutral); - RunImpl( + return RunImpl( &GetBank(banks, b_particle, "REC::Particle"), includeDefaultBanks ? &GetBank(banks, b_track, "REC::Track") : nullptr, includeDefaultBanks ? &GetBank(banks, b_calorimeter, "REC::Calorimeter") : nullptr, @@ -67,7 +67,7 @@ namespace iguana::clas12 { &GetBank(banks, b_result, "REC::Particle::Sector")); } - void SectorFinder::RunImpl( + bool SectorFinder::RunImpl( hipo::bank const* particleBank, hipo::bank const* trackBank, hipo::bank const* calBank, @@ -151,6 +151,7 @@ namespace iguana::clas12 { } ShowBank(*resultBank, Logger::Header("CREATED BANK")); + return true; } void SectorFinder::GetListsSectorPindex(hipo::bank const& bank, std::vector& sectors, std::vector& pindices) const diff --git a/src/iguana/algorithms/clas12/SectorFinder/Algorithm.h b/src/iguana/algorithms/clas12/SectorFinder/Algorithm.h index 0bb2b56aa..2aaaad256 100644 --- a/src/iguana/algorithms/clas12/SectorFinder/Algorithm.h +++ b/src/iguana/algorithms/clas12/SectorFinder/Algorithm.h @@ -37,27 +37,30 @@ namespace iguana::clas12 { public: void Start(hipo::banklist& banks) override; - void Run(hipo::banklist& banks) const override; + bool Run(hipo::banklist& banks) const override; void Stop() override; - /// @brief run function, using track, calorimeter, and scintillator banks for both charged and neutral particles + /// @run_function + /// uses track, calorimeter, and scintillator banks for both charged and neutral particles /// @see this algorithm contains multiple run functions, for if you prefer to use other banks /// @param [in] particleBank `REC::Particle` /// @param [in] trackBank `REC::Track` /// @param [in] calBank `REC::Calorimeter` /// @param [in] scintBank `REC::Scintillator` /// @param [out] resultBank the output `REC::Particle::Sector` bank - void Run( + /// @run_function_returns_true + bool Run( hipo::bank const& particleBank, hipo::bank const& trackBank, hipo::bank const& calBank, hipo::bank const& scintBank, hipo::bank& resultBank) const { - RunImpl(&particleBank, &trackBank, &calBank, &scintBank, nullptr, nullptr, &resultBank); + return RunImpl(&particleBank, &trackBank, &calBank, &scintBank, nullptr, nullptr, &resultBank); } - /// @brief run function, using track, calorimeter, and scintillator banks for charged particles, and a custom bank for neutral particles + /// @run_function + /// uses track, calorimeter, and scintillator banks for charged particles, and a custom bank for neutral particles /// @see this algorithm contains multiple run functions, for if you prefer to use other banks /// @param [in] particleBank `REC::Particle` /// @param [in] trackBank `REC::Track` @@ -65,7 +68,8 @@ namespace iguana::clas12 { /// @param [in] scintBank `REC::Scintillator` /// @param [in] userChargedBank custom bank used to obtain charged-particles' sectors /// @param [out] resultBank the output `REC::Particle::Sector` bank - void RunWithCustomChargedBank( + /// @run_function_returns_true + bool RunWithCustomChargedBank( hipo::bank const& particleBank, hipo::bank const& trackBank, hipo::bank const& calBank, @@ -73,10 +77,11 @@ namespace iguana::clas12 { hipo::bank const& userChargedBank, hipo::bank& resultBank) const { - RunImpl(&particleBank, &trackBank, &calBank, &scintBank, &userChargedBank, nullptr, &resultBank); + return RunImpl(&particleBank, &trackBank, &calBank, &scintBank, &userChargedBank, nullptr, &resultBank); } - /// @brief run function, using track, calorimeter, and scintillator banks for neutral particles, and a custom bank for charged particles + /// @run_function + /// uses track, calorimeter, and scintillator banks for neutral particles, and a custom bank for charged particles /// @see this algorithm contains multiple run functions, for if you prefer to use other banks /// @param [in] particleBank `REC::Particle` /// @param [in] trackBank `REC::Track` @@ -84,7 +89,8 @@ namespace iguana::clas12 { /// @param [in] scintBank `REC::Scintillator` /// @param [in] userNeutralBank custom bank used to obtain neutral-particles' sectors /// @param [out] resultBank the output `REC::Particle::Sector` bank - void RunWithCustomNeutralBank( + /// @run_function_returns_true + bool RunWithCustomNeutralBank( hipo::bank const& particleBank, hipo::bank const& trackBank, hipo::bank const& calBank, @@ -92,22 +98,24 @@ namespace iguana::clas12 { hipo::bank const& userNeutralBank, hipo::bank& resultBank) const { - RunImpl(&particleBank, &trackBank, &calBank, &scintBank, nullptr, &userNeutralBank, &resultBank); + return RunImpl(&particleBank, &trackBank, &calBank, &scintBank, nullptr, &userNeutralBank, &resultBank); } - /// @brief run function, using custom banks for both charged and neutral particles + /// @run_function + /// uses custom banks for both charged and neutral particles /// @see this algorithm contains multiple run functions, for if you prefer to use other banks /// @param [in] particleBank `REC::Particle` /// @param [in] userChargedBank custom bank used to obtain charged-particles' sectors /// @param [in] userNeutralBank custom bank used to obtain neutral-particles' sectors /// @param [out] resultBank the output `REC::Particle::Sector` bank - void RunWithCustomBanks( + /// @run_function_returns_true + bool RunWithCustomBanks( hipo::bank const& particleBank, hipo::bank const& userChargedBank, hipo::bank const& userNeutralBank, hipo::bank& resultBank) const { - RunImpl(&particleBank, nullptr, nullptr, nullptr, &userChargedBank, &userNeutralBank, &resultBank); + return RunImpl(&particleBank, nullptr, nullptr, nullptr, &userChargedBank, &userNeutralBank, &resultBank); } /// @action_function{scalar creator} for a given particle with index `pindex_particle`, get its sector from @@ -227,7 +235,8 @@ namespace iguana::clas12 { /// @param [in] userChargedBank custom bank used to obtain charged-particles' sectors /// @param [in] userNeutralBank custom bank used to obtain neutral-particles' sectors /// @param [out] resultBank the output `REC::Particle::Sector` bank - void RunImpl( + /// @run_function_returns_true + bool RunImpl( hipo::bank const* particleBank, hipo::bank const* trackBank, hipo::bank const* calBank, diff --git a/src/iguana/algorithms/clas12/SectorFinder/Validator.cc b/src/iguana/algorithms/clas12/SectorFinder/Validator.cc index 70442affc..ac5e7c6f7 100644 --- a/src/iguana/algorithms/clas12/SectorFinder/Validator.cc +++ b/src/iguana/algorithms/clas12/SectorFinder/Validator.cc @@ -52,7 +52,7 @@ namespace iguana::clas12 { } - void SectorFinderValidator::Run(hipo::banklist& banks) const + bool SectorFinderValidator::Run(hipo::banklist& banks) const { auto& particle_bank = GetBank(banks, b_particle, "REC::Particle"); @@ -101,6 +101,7 @@ namespace iguana::clas12 { u_YvsX.at(pdg).at(sector - 1)->Fill(x, y); } + return true; } diff --git a/src/iguana/algorithms/clas12/SectorFinder/Validator.h b/src/iguana/algorithms/clas12/SectorFinder/Validator.h index 089c1afaa..20d92d68f 100644 --- a/src/iguana/algorithms/clas12/SectorFinder/Validator.h +++ b/src/iguana/algorithms/clas12/SectorFinder/Validator.h @@ -19,7 +19,7 @@ namespace iguana::clas12 { public: void Start(hipo::banklist& banks) override; - void Run(hipo::banklist& banks) const override; + bool Run(hipo::banklist& banks) const override; void Stop() override; private: diff --git a/src/iguana/algorithms/clas12/TrajLinker/Algorithm.cc b/src/iguana/algorithms/clas12/TrajLinker/Algorithm.cc index 9c94332e5..a624ec15f 100644 --- a/src/iguana/algorithms/clas12/TrajLinker/Algorithm.cc +++ b/src/iguana/algorithms/clas12/TrajLinker/Algorithm.cc @@ -26,7 +26,7 @@ namespace iguana::clas12 { i_r3_z = result_schema.getEntryOrder("r3_z"); } - void TrajLinker::Run(hipo::banklist& banks) const + bool TrajLinker::Run(hipo::banklist& banks) const { auto& bank_particle = GetBank(banks, b_particle, "REC::Particle"); auto& bank_traj = GetBank(banks, b_traj, "REC::Traj"); @@ -107,6 +107,7 @@ namespace iguana::clas12 { bank_result.putFloat(i_r3_z, row_particle, link_particle.r3_z); } ShowBank(bank_result, Logger::Header("CREATED BANK")); + return true; } void TrajLinker::Stop() diff --git a/src/iguana/algorithms/clas12/TrajLinker/Algorithm.h b/src/iguana/algorithms/clas12/TrajLinker/Algorithm.h index 69b7aa04e..aff26134d 100644 --- a/src/iguana/algorithms/clas12/TrajLinker/Algorithm.h +++ b/src/iguana/algorithms/clas12/TrajLinker/Algorithm.h @@ -25,7 +25,7 @@ namespace iguana::clas12 { public: void Start(hipo::banklist& banks) override; - void Run(hipo::banklist& banks) const override; + bool Run(hipo::banklist& banks) const override; void Stop() override; /// @returns the DC sector given (x,y,z), or `-1` if failed diff --git a/src/iguana/algorithms/clas12/ZVertexFilter/Algorithm.cc b/src/iguana/algorithms/clas12/ZVertexFilter/Algorithm.cc index e91a9fd9c..ca6cbe060 100644 --- a/src/iguana/algorithms/clas12/ZVertexFilter/Algorithm.cc +++ b/src/iguana/algorithms/clas12/ZVertexFilter/Algorithm.cc @@ -18,14 +18,14 @@ namespace iguana::clas12 { b_config = GetBankIndex(banks, "RUN::config"); } - void ZVertexFilter::Run(hipo::banklist& banks) const + bool ZVertexFilter::Run(hipo::banklist& banks) const { - Run( + return Run( GetBank(banks, b_particle, "REC::Particle"), GetBank(banks, b_config, "RUN::config")); } - void ZVertexFilter::Run(hipo::bank& particleBank, hipo::bank const& configBank) const + bool ZVertexFilter::Run(hipo::bank& particleBank, hipo::bank const& configBank) const { // dump the bank ShowBank(particleBank, Logger::Header("INPUT PARTICLES")); @@ -45,6 +45,7 @@ namespace iguana::clas12 { // dump the modified bank ShowBank(particleBank, Logger::Header("OUTPUT PARTICLES")); + return true; } concurrent_key_t ZVertexFilter::PrepareEvent(int const runnum) const { diff --git a/src/iguana/algorithms/clas12/ZVertexFilter/Algorithm.h b/src/iguana/algorithms/clas12/ZVertexFilter/Algorithm.h index 4283c0aba..f7e470270 100644 --- a/src/iguana/algorithms/clas12/ZVertexFilter/Algorithm.h +++ b/src/iguana/algorithms/clas12/ZVertexFilter/Algorithm.h @@ -22,13 +22,14 @@ namespace iguana::clas12 { public: void Start(hipo::banklist& banks) override; - void Run(hipo::banklist& banks) const override; + bool Run(hipo::banklist& banks) const override; void Stop() override; - /// run function + /// @run_function /// @param [in,out] particleBank `REC::Particle`, which will be filtered /// @param [in] configBank `RUN::config` - void Run(hipo::bank& particleBank, hipo::bank const& configBank) const; + /// @run_function_returns_true + bool Run(hipo::bank& particleBank, hipo::bank const& configBank) const; /// @action_function{reload} prepare the event /// @when_to_call{for each event} diff --git a/src/iguana/algorithms/clas12/ZVertexFilter/Validator.cc b/src/iguana/algorithms/clas12/ZVertexFilter/Validator.cc index daaf3206a..433e5babf 100644 --- a/src/iguana/algorithms/clas12/ZVertexFilter/Validator.cc +++ b/src/iguana/algorithms/clas12/ZVertexFilter/Validator.cc @@ -47,7 +47,7 @@ namespace iguana::clas12 { } - void ZVertexFilterValidator::Run(hipo::banklist& banks) const + bool ZVertexFilterValidator::Run(hipo::banklist& banks) const { auto& particle_bank = GetBank(banks, b_particle, "REC::Particle"); @@ -80,6 +80,7 @@ namespace iguana::clas12 { u_zvertexplots.at(pdg).at(1)->Fill(vz); } } + return true; } void ZVertexFilterValidator::Stop() diff --git a/src/iguana/algorithms/clas12/ZVertexFilter/Validator.h b/src/iguana/algorithms/clas12/ZVertexFilter/Validator.h index f62c36b7b..4ea393b2b 100644 --- a/src/iguana/algorithms/clas12/ZVertexFilter/Validator.h +++ b/src/iguana/algorithms/clas12/ZVertexFilter/Validator.h @@ -17,7 +17,7 @@ namespace iguana::clas12 { public: void Start(hipo::banklist& banks) override; - void Run(hipo::banklist& banks) const override; + bool Run(hipo::banklist& banks) const override; void Stop() override; private: diff --git a/src/iguana/algorithms/example/ExampleAlgorithm/Algorithm.cc b/src/iguana/algorithms/example/ExampleAlgorithm/Algorithm.cc index bab21861d..744448c54 100644 --- a/src/iguana/algorithms/example/ExampleAlgorithm/Algorithm.cc +++ b/src/iguana/algorithms/example/ExampleAlgorithm/Algorithm.cc @@ -59,7 +59,7 @@ namespace iguana::example { // # - try to avoid expensive operations here; instead, put them in the `Start` method // # if it is reasonable to do so // ############################################################################ - void ExampleAlgorithm::Run(hipo::banklist& banks) const + bool ExampleAlgorithm::Run(hipo::banklist& banks) const { // ############################################################################ // # get the banks; here we just need `REC::Particle` @@ -98,6 +98,11 @@ namespace iguana::example { // # dump the modified bank (only if the log level is low enough); this is also optional // ############################################################################ ShowBank(particleBank, Logger::Header("OUTPUT PARTICLES")); + + // ############################################################################ + // # return true or false, used as an event-level filter + // ############################################################################ + return true; } diff --git a/src/iguana/algorithms/example/ExampleAlgorithm/Algorithm.h b/src/iguana/algorithms/example/ExampleAlgorithm/Algorithm.h index 64137e93c..29dc1f440 100644 --- a/src/iguana/algorithms/example/ExampleAlgorithm/Algorithm.h +++ b/src/iguana/algorithms/example/ExampleAlgorithm/Algorithm.h @@ -63,7 +63,7 @@ namespace iguana::example { // # - each algorithm must have these methods (even if they do nothing) // ############################################################################ void Start(hipo::banklist& banks) override; - void Run(hipo::banklist& banks) const override; + bool Run(hipo::banklist& banks) const override; void Stop() override; // ############################################################################ diff --git a/src/iguana/algorithms/physics/Depolarization/Algorithm.cc b/src/iguana/algorithms/physics/Depolarization/Algorithm.cc index 842573679..e13e4c58d 100644 --- a/src/iguana/algorithms/physics/Depolarization/Algorithm.cc +++ b/src/iguana/algorithms/physics/Depolarization/Algorithm.cc @@ -20,7 +20,7 @@ namespace iguana::physics { /////////////////////////////////////////////////////////////////////////////// - void Depolarization::Run(hipo::banklist& banks) const + bool Depolarization::Run(hipo::banklist& banks) const { auto& inc_kin_bank = GetBank(banks, b_inc_kin, "physics::InclusiveKinematics"); auto& result_bank = GetBank(banks, b_result, GetClassName()); @@ -59,6 +59,7 @@ namespace iguana::physics { } ShowBank(result_bank, Logger::Header("CREATED BANK")); + return true; } /////////////////////////////////////////////////////////////////////////////// diff --git a/src/iguana/algorithms/physics/Depolarization/Algorithm.h b/src/iguana/algorithms/physics/Depolarization/Algorithm.h index e041d5ad1..254ea5e7f 100644 --- a/src/iguana/algorithms/physics/Depolarization/Algorithm.h +++ b/src/iguana/algorithms/physics/Depolarization/Algorithm.h @@ -24,7 +24,7 @@ namespace iguana::physics { public: void Start(hipo::banklist& banks) override; - void Run(hipo::banklist& banks) const override; + bool Run(hipo::banklist& banks) const override; void Stop() override; /// @action_function{scalar creator} compute depolarization factors diff --git a/src/iguana/algorithms/physics/Depolarization/Validator.cc b/src/iguana/algorithms/physics/Depolarization/Validator.cc index ba5ca07a7..4915f3745 100644 --- a/src/iguana/algorithms/physics/Depolarization/Validator.cc +++ b/src/iguana/algorithms/physics/Depolarization/Validator.cc @@ -93,7 +93,7 @@ namespace iguana::physics { } - void DepolarizationValidator::Run(hipo::banklist& banks) const + bool DepolarizationValidator::Run(hipo::banklist& banks) const { // calculate kinematics m_algo_seq->Run(banks); @@ -103,7 +103,7 @@ namespace iguana::physics { // skip events with empty bank(s) if(inc_kin_bank.getRowList().size() == 0 || depol_bank.getRowList().size() == 0) { m_log->Debug("skip this event, since it has no kinematics results"); - return; + return false; } // lock mutex and fill the plots @@ -116,6 +116,7 @@ namespace iguana::physics { for(auto& plot : plots_vs_y) plot.hist->Fill(inc_kin_bank.getDouble("y", row), plot.get_val(depol_bank, row)); } + return true; } diff --git a/src/iguana/algorithms/physics/Depolarization/Validator.h b/src/iguana/algorithms/physics/Depolarization/Validator.h index b06b74936..5f7f9b304 100644 --- a/src/iguana/algorithms/physics/Depolarization/Validator.h +++ b/src/iguana/algorithms/physics/Depolarization/Validator.h @@ -17,7 +17,7 @@ namespace iguana::physics { public: void Start(hipo::banklist& banks) override; - void Run(hipo::banklist& banks) const override; + bool Run(hipo::banklist& banks) const override; void Stop() override; private: diff --git a/src/iguana/algorithms/physics/DihadronKinematics/Algorithm.cc b/src/iguana/algorithms/physics/DihadronKinematics/Algorithm.cc index 823afef82..7cebeb129 100644 --- a/src/iguana/algorithms/physics/DihadronKinematics/Algorithm.cc +++ b/src/iguana/algorithms/physics/DihadronKinematics/Algorithm.cc @@ -50,7 +50,7 @@ namespace iguana::physics { /////////////////////////////////////////////////////////////////////////////// - void DihadronKinematics::Run(hipo::banklist& banks) const + bool DihadronKinematics::Run(hipo::banklist& banks) const { auto& particle_bank = GetBank(banks, b_particle, "REC::Particle"); auto& inc_kin_bank = GetBank(banks, b_inc_kin, "physics::InclusiveKinematics"); @@ -59,7 +59,7 @@ namespace iguana::physics { if(particle_bank.getRowList().empty() || inc_kin_bank.getRowList().empty()) { m_log->Debug("skip this event, since not all required banks have entries"); - return; + return false; } // get beam and target momenta @@ -195,6 +195,7 @@ namespace iguana::physics { } ShowBank(result_bank, Logger::Header("CREATED BANK")); + return true; } /////////////////////////////////////////////////////////////////////////////// diff --git a/src/iguana/algorithms/physics/DihadronKinematics/Algorithm.h b/src/iguana/algorithms/physics/DihadronKinematics/Algorithm.h index 8052c52ef..0b6dc929e 100644 --- a/src/iguana/algorithms/physics/DihadronKinematics/Algorithm.h +++ b/src/iguana/algorithms/physics/DihadronKinematics/Algorithm.h @@ -46,7 +46,7 @@ namespace iguana::physics { public: void Start(hipo::banklist& banks) override; - void Run(hipo::banklist& banks) const override; + bool Run(hipo::banklist& banks) const override; void Stop() override; /// @brief form dihadrons by pairing hadrons diff --git a/src/iguana/algorithms/physics/DihadronKinematics/Validator.cc b/src/iguana/algorithms/physics/DihadronKinematics/Validator.cc index a57c08d2a..a3b78c44f 100644 --- a/src/iguana/algorithms/physics/DihadronKinematics/Validator.cc +++ b/src/iguana/algorithms/physics/DihadronKinematics/Validator.cc @@ -81,7 +81,7 @@ namespace iguana::physics { } - void DihadronKinematicsValidator::Run(hipo::banklist& banks) const + bool DihadronKinematicsValidator::Run(hipo::banklist& banks) const { // calculate kinematics m_algo_seq->Run(banks); @@ -90,7 +90,7 @@ namespace iguana::physics { // skip events with no dihadrons if(result_bank.getRowList().size() == 0) { m_log->Debug("skip this event, since it has no kinematics results"); - return; + return false; } // lock mutex and fill the plots @@ -99,6 +99,7 @@ namespace iguana::physics { for(auto& plot : plot_list) plot.hist->Fill(plot.get_val(result_bank, row)); } + return true; } diff --git a/src/iguana/algorithms/physics/DihadronKinematics/Validator.h b/src/iguana/algorithms/physics/DihadronKinematics/Validator.h index d7ca5ee6f..9abaec24d 100644 --- a/src/iguana/algorithms/physics/DihadronKinematics/Validator.h +++ b/src/iguana/algorithms/physics/DihadronKinematics/Validator.h @@ -18,7 +18,7 @@ namespace iguana::physics { public: void Start(hipo::banklist& banks) override; - void Run(hipo::banklist& banks) const override; + bool Run(hipo::banklist& banks) const override; void Stop() override; private: diff --git a/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.cc b/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.cc index e0774cfe0..5583bc2fb 100644 --- a/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.cc +++ b/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.cc @@ -74,15 +74,15 @@ namespace iguana::physics { /////////////////////////////////////////////////////////////////////////////// - void InclusiveKinematics::Run(hipo::banklist& banks) const + bool InclusiveKinematics::Run(hipo::banklist& banks) const { - Run( + return Run( GetBank(banks, b_particle, "REC::Particle"), GetBank(banks, b_config, "RUN::config"), GetBank(banks, b_result, GetClassName())); } - void InclusiveKinematics::Run( + bool InclusiveKinematics::Run( hipo::bank const& particle_bank, hipo::bank const& config_bank, hipo::bank& result_bank) const @@ -94,7 +94,7 @@ namespace iguana::physics { auto lepton_pindex = FindScatteredLepton(particle_bank, key); if(lepton_pindex < 0) { ShowBank(result_bank, Logger::Header("CREATED BANK IS EMPTY")); - return; + return false; } auto result_vars = ComputeFromLepton( @@ -119,6 +119,7 @@ namespace iguana::physics { result_bank.putDouble(i_targetM, 0, result_vars.targetM); ShowBank(result_bank, Logger::Header("CREATED BANK")); + return true; } /////////////////////////////////////////////////////////////////////////////// diff --git a/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h b/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h index 5aec7bd28..d553ffe14 100644 --- a/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h +++ b/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h @@ -30,14 +30,16 @@ namespace iguana::physics { public: void Start(hipo::banklist& banks) override; - void Run(hipo::banklist& banks) const override; + bool Run(hipo::banklist& banks) const override; void Stop() override; - /// run function + /// @run_function /// @param [in] particle_bank `REC::Particle` /// @param [in] config_bank `RUN::config` /// @param [out] result_bank `%physics::InclusiveKinematics` - void Run( + /// @returns `true` if the kinematics were calculated, _e.g._, if the calculations are performed using + /// the scattered lepton, and no scattered lepton was found, `false` will be returned + bool Run( hipo::bank const& particle_bank, hipo::bank const& config_bank, hipo::bank& result_bank) const; diff --git a/src/iguana/algorithms/physics/InclusiveKinematics/Validator.cc b/src/iguana/algorithms/physics/InclusiveKinematics/Validator.cc index 97b6b52c5..ed8ba80e6 100644 --- a/src/iguana/algorithms/physics/InclusiveKinematics/Validator.cc +++ b/src/iguana/algorithms/physics/InclusiveKinematics/Validator.cc @@ -50,7 +50,7 @@ namespace iguana::physics { } - void InclusiveKinematicsValidator::Run(hipo::banklist& banks) const + bool InclusiveKinematicsValidator::Run(hipo::banklist& banks) const { // calculate kinematics m_algo_seq->Run(banks); @@ -59,7 +59,7 @@ namespace iguana::physics { if(result_bank.getRowList().size() == 0) { m_log->Debug("skip this event, since it has no inclusive kinematics results"); - return; + return false; } if(result_bank.getRowList().size() > 1) { m_log->Warn("found event with more than 1 inclusive kinematics bank rows; only the first row will be used"); @@ -95,6 +95,7 @@ namespace iguana::physics { Q2_vs_W->Fill(W, Q2); y_dist->Fill(y); nu_dist->Fill(nu); + return true; } diff --git a/src/iguana/algorithms/physics/InclusiveKinematics/Validator.h b/src/iguana/algorithms/physics/InclusiveKinematics/Validator.h index f5550cf33..4df6b859b 100644 --- a/src/iguana/algorithms/physics/InclusiveKinematics/Validator.h +++ b/src/iguana/algorithms/physics/InclusiveKinematics/Validator.h @@ -18,7 +18,7 @@ namespace iguana::physics { public: void Start(hipo::banklist& banks) override; - void Run(hipo::banklist& banks) const override; + bool Run(hipo::banklist& banks) const override; void Stop() override; private: diff --git a/src/iguana/algorithms/physics/SingleHadronKinematics/Algorithm.cc b/src/iguana/algorithms/physics/SingleHadronKinematics/Algorithm.cc index 10136d0e6..b0297c2ef 100644 --- a/src/iguana/algorithms/physics/SingleHadronKinematics/Algorithm.cc +++ b/src/iguana/algorithms/physics/SingleHadronKinematics/Algorithm.cc @@ -34,7 +34,7 @@ namespace iguana::physics { /////////////////////////////////////////////////////////////////////////////// - void SingleHadronKinematics::Run(hipo::banklist& banks) const + bool SingleHadronKinematics::Run(hipo::banklist& banks) const { auto& particle_bank = GetBank(banks, b_particle, "REC::Particle"); auto& inc_kin_bank = GetBank(banks, b_inc_kin, "physics::InclusiveKinematics"); @@ -43,7 +43,7 @@ namespace iguana::physics { if(particle_bank.getRowList().empty() || inc_kin_bank.getRowList().empty()) { m_log->Debug("skip this event, since not all required banks have entries"); - return; + return false; } // get beam and target momenta @@ -159,6 +159,7 @@ namespace iguana::physics { result_bank.getMutableRowList().setList(result_bank_rowlist); ShowBank(result_bank, Logger::Header("CREATED BANK")); + return true; } /////////////////////////////////////////////////////////////////////////////// diff --git a/src/iguana/algorithms/physics/SingleHadronKinematics/Algorithm.h b/src/iguana/algorithms/physics/SingleHadronKinematics/Algorithm.h index 3cacca337..3451309d4 100644 --- a/src/iguana/algorithms/physics/SingleHadronKinematics/Algorithm.h +++ b/src/iguana/algorithms/physics/SingleHadronKinematics/Algorithm.h @@ -35,7 +35,7 @@ namespace iguana::physics { public: void Start(hipo::banklist& banks) override; - void Run(hipo::banklist& banks) const override; + bool Run(hipo::banklist& banks) const override; void Stop() override; private: diff --git a/src/iguana/algorithms/physics/SingleHadronKinematics/Validator.cc b/src/iguana/algorithms/physics/SingleHadronKinematics/Validator.cc index c1e839e2b..a62de941c 100644 --- a/src/iguana/algorithms/physics/SingleHadronKinematics/Validator.cc +++ b/src/iguana/algorithms/physics/SingleHadronKinematics/Validator.cc @@ -72,7 +72,7 @@ namespace iguana::physics { } - void SingleHadronKinematicsValidator::Run(hipo::banklist& banks) const + bool SingleHadronKinematicsValidator::Run(hipo::banklist& banks) const { // calculate kinematics m_algo_seq->Run(banks); @@ -81,7 +81,7 @@ namespace iguana::physics { // skip events with no hadrons if(result_bank.getRowList().size() == 0) { m_log->Debug("skip this event, since it has no kinematics results"); - return; + return false; } // lock mutex and fill the plots @@ -90,6 +90,7 @@ namespace iguana::physics { for(auto& plot : plot_list) plot.hist->Fill(plot.get_val(result_bank, row)); } + return true; } diff --git a/src/iguana/algorithms/physics/SingleHadronKinematics/Validator.h b/src/iguana/algorithms/physics/SingleHadronKinematics/Validator.h index 3017da570..be4515cdb 100644 --- a/src/iguana/algorithms/physics/SingleHadronKinematics/Validator.h +++ b/src/iguana/algorithms/physics/SingleHadronKinematics/Validator.h @@ -18,7 +18,7 @@ namespace iguana::physics { public: void Start(hipo::banklist& banks) override; - void Run(hipo::banklist& banks) const override; + bool Run(hipo::banklist& banks) const override; void Stop() override; private: From 6e619559e5f6aee8ab9185e6093327dfc1a2e6bf Mon Sep 17 00:00:00 2001 From: Christopher Dilks Date: Thu, 11 Sep 2025 12:45:44 -0400 Subject: [PATCH 09/26] fix: bank name --- examples/iguana_ex_cpp_00_run_functions_with_banks.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/iguana_ex_cpp_00_run_functions_with_banks.cc b/examples/iguana_ex_cpp_00_run_functions_with_banks.cc index dcc32c11f..1892a8e8d 100644 --- a/examples/iguana_ex_cpp_00_run_functions_with_banks.cc +++ b/examples/iguana_ex_cpp_00_run_functions_with_banks.cc @@ -38,7 +38,7 @@ int main(int argc, char** argv) hipo::dictionary dict; reader.readDictionary(dict); hipo::bank bank_config(dict.getSchema("RUN::config")); - hipo::bank bank_particle(dict.getSchema("RUN::Particle")); + hipo::bank bank_particle(dict.getSchema("REC::Particle")); hipo::bank bank_calorimeter(dict.getSchema("REC::Calorimeter")); hipo::bank bank_track(dict.getSchema("REC::Track")); hipo::bank bank_scintillator(dict.getSchema("REC::Scintillator")); From 3f4d81f30aa6336f8671b12cc84d58df6ef96b87 Mon Sep 17 00:00:00 2001 From: Christopher Dilks Date: Thu, 11 Sep 2025 19:05:34 -0400 Subject: [PATCH 10/26] fix: make it easier to get created bank names from `AlgorithmSequence` --- .../iguana_ex_python_00_run_functions.py | 2 +- examples/iguana_ex_cpp_00_run_functions.cc | 2 +- src/iguana/algorithms/AlgorithmSequence.cc | 24 +++++++++-- src/iguana/algorithms/AlgorithmSequence.h | 42 ++++++++++++------- 4 files changed, 49 insertions(+), 21 deletions(-) diff --git a/bind/python/iguana_ex_python_00_run_functions.py b/bind/python/iguana_ex_python_00_run_functions.py index 83e944196..ff013f341 100755 --- a/bind/python/iguana_ex_python_00_run_functions.py +++ b/bind/python/iguana_ex_python_00_run_functions.py @@ -56,7 +56,7 @@ seq.Start(banks) # get the name of newly created banks (if you don't want to look them up in the documentation) -sector_finder_bank_name = seq.Get("clas12::SectorFinder").GetCreatedBankName(); +sector_finder_bank_name = seq.GetCreatedBankName("clas12::SectorFinder"); # get bank index, for each bank we want to use after Iguana algorithms run # NOTE: new banks from creator algorithms are initialized by `Start` diff --git a/examples/iguana_ex_cpp_00_run_functions.cc b/examples/iguana_ex_cpp_00_run_functions.cc index 4cc6c72ba..8bb100335 100644 --- a/examples/iguana_ex_cpp_00_run_functions.cc +++ b/examples/iguana_ex_cpp_00_run_functions.cc @@ -61,7 +61,7 @@ int main(int argc, char** argv) seq.Start(banks); // get the name of newly created banks (or you can just get them from the documentation) - auto sector_finder_bank_name = seq.Get("clas12::SectorFinder")->GetCreatedBankName(); + auto sector_finder_bank_name = seq.GetCreatedBankName("clas12::SectorFinder"); // get bank index, for each bank we want to use after Iguana algorithms run // NOTE: new banks from creator algorithms are initialized by `Start` diff --git a/src/iguana/algorithms/AlgorithmSequence.cc b/src/iguana/algorithms/AlgorithmSequence.cc index c48951e76..56c6d1d97 100644 --- a/src/iguana/algorithms/AlgorithmSequence.cc +++ b/src/iguana/algorithms/AlgorithmSequence.cc @@ -23,14 +23,14 @@ namespace iguana { algo->Stop(); } - void AlgorithmSequence::Add(std::string const& class_name, std::string const& instance_name) + void AlgorithmSequence::Add(std::string const& algo_class_name, std::string const& algo_instance_name) { - auto algo = AlgorithmFactory::Create(class_name); + auto algo = AlgorithmFactory::Create(algo_class_name); if(algo == nullptr) { - m_log->Error("algorithm '{}' does not exist", class_name); + m_log->Error("algorithm '{}' does not exist", algo_class_name); throw std::runtime_error("AlgorithmFactory cannot create non-existent algorithm"); } - algo->SetName(instance_name == "" ? class_name : instance_name); + algo->SetName(algo_instance_name == "" ? algo_class_name : algo_instance_name); Add(std::move(algo)); } @@ -63,6 +63,22 @@ namespace iguana { Algorithm::SetName(name); } + std::vector AlgorithmSequence::GetCreatedBankNames(std::string const& algo_instance_name) const noexcept(false) + { + if(auto it{m_algo_names.find(algo_instance_name)}; it != m_algo_names.end()) + return m_sequence[it->second]->GetCreatedBankNames(); + m_log->Error("cannot find algorithm '{}' in sequence", algo_instance_name); + throw std::runtime_error("GetCreatedBankNames failed"); + } + + std::string AlgorithmSequence::GetCreatedBankName(std::string const& algo_instance_name) const noexcept(false) + { + if(auto it{m_algo_names.find(algo_instance_name)}; it != m_algo_names.end()) + return m_sequence[it->second]->GetCreatedBankName(); + m_log->Error("cannot find algorithm '{}' in sequence", algo_instance_name); + throw std::runtime_error("GetCreatedBankName failed"); + } + void AlgorithmSequence::PrintSequence(Logger::Level level) const { m_log->Print(level, "algorithms in this sequence:"); diff --git a/src/iguana/algorithms/AlgorithmSequence.h b/src/iguana/algorithms/AlgorithmSequence.h index 49397cb09..6a2275ac2 100644 --- a/src/iguana/algorithms/AlgorithmSequence.h +++ b/src/iguana/algorithms/AlgorithmSequence.h @@ -26,10 +26,10 @@ namespace iguana { /// @code /// Add("iguana::MyAlgorithm", "my_algorithm_name"); /// @endcode - /// @param class_name the name of the algorithm class - /// @param instance_name a user-specified unique name for this algorithm instance; - /// if not specified, `class_name` will be used - void Add(std::string const& class_name, std::string const& instance_name = ""); + /// @param algo_class_name the name of the algorithm class + /// @param algo_instance_name a user-specified unique name for this algorithm instance; + /// if not specified, `algo_class_name` will be used + void Add(std::string const& algo_class_name, std::string const& algo_instance_name = ""); /// Create and add an algorithm to the sequence. /// @@ -37,15 +37,15 @@ namespace iguana { /// @code /// Add("my_algorithm_name"); /// @endcode - /// @param instance_name a user-specified unique name for this algorithm instance; + /// @param algo_instance_name a user-specified unique name for this algorithm instance; /// if not specified, the class name will be used template - void Add(std::string_view instance_name = "") + void Add(std::string_view algo_instance_name = "") { - if(instance_name == "") + if(algo_instance_name == "") Add(std::make_unique()); else - Add(std::make_unique(instance_name)); + Add(std::make_unique(algo_instance_name)); } /// Add an existing algorithm to the sequence. The `AlgorithmSequence` instance will take ownership of the algorithm @@ -64,32 +64,44 @@ namespace iguana { /// @code /// Get("my_algorithm_name"); /// @endcode - /// @param instance_name the instance name of the algorithm + /// @param algo_instance_name the instance name of the algorithm /// @return a reference to the algorithm template - ALGORITHM* Get(std::string const& instance_name) + ALGORITHM* Get(std::string const& algo_instance_name) { - if(auto it{m_algo_names.find(instance_name)}; it != m_algo_names.end()) + if(auto it{m_algo_names.find(algo_instance_name)}; it != m_algo_names.end()) return dynamic_cast(m_sequence[it->second].get()); - m_log->Error("cannot find algorithm '{}' in sequence", instance_name); + m_log->Error("cannot find algorithm '{}' in sequence", algo_instance_name); throw std::runtime_error("cannot Get algorithm"); } /// Set an algorithm option /// @see `Algorithm::SetOption` - /// @param algo_name the algorithm instance name + /// @param algo_instance_name the algorithm instance name /// @param key the option name /// @param val the option value template - void SetOption(std::string const& algo_name, std::string const& key, const OPTION_TYPE val) + void SetOption(std::string const& algo_instance_name, std::string const& key, const OPTION_TYPE val) { - Get(algo_name)->SetOption(key, val); + Get(algo_instance_name)->SetOption(key, val); } /// Set the name of this sequence /// @param name the new name void SetName(std::string_view name); + /// Get the list of created bank names, for creator-type algorithms + /// @see `AlgorithmSequence::GetCreatedBankName` for algorithms which create only one bank + /// @param algo_instance_name the algorithm instance name + /// @returns the list of new bank names + std::vector GetCreatedBankNames(std::string const& algo_instance_name) const noexcept(false); + + /// Get the created bank name, for creator-type algorithms which create only one new bank + /// @see `AlgorithmSequence::GetCreatedBankNames` for algorithms which create more than one new bank + /// @param algo_instance_name the algorithm instance name + /// @returns the new bank name + std::string GetCreatedBankName(std::string const& algo_instance_name) const noexcept(false); + /// Print the names of the algorithms in this sequence /// @param level the log level of the printout void PrintSequence(Logger::Level level = Logger::info) const; From d23bd9228ff190fa01b8db07fda96f104a040441 Mon Sep 17 00:00:00 2001 From: Christopher Dilks Date: Fri, 12 Sep 2025 10:18:20 -0400 Subject: [PATCH 11/26] feat: more `Run` functions --- src/iguana/algorithms/AlgorithmSequence.cc | 2 + src/iguana/algorithms/AlgorithmSequence.h | 4 + .../clas12/CalorimeterLinker/Algorithm.cc | 14 ++- .../clas12/CalorimeterLinker/Algorithm.h | 12 ++- .../clas12/FTEnergyCorrection/Algorithm.cc | 9 +- .../clas12/FTEnergyCorrection/Algorithm.h | 5 + .../clas12/FiducialFilter/Algorithm.cc | 98 +++++++++---------- .../clas12/FiducialFilter/Algorithm.h | 15 ++- .../clas12/FiducialFilter/Config.yaml | 1 - 9 files changed, 100 insertions(+), 60 deletions(-) diff --git a/src/iguana/algorithms/AlgorithmSequence.cc b/src/iguana/algorithms/AlgorithmSequence.cc index 56c6d1d97..6333da838 100644 --- a/src/iguana/algorithms/AlgorithmSequence.cc +++ b/src/iguana/algorithms/AlgorithmSequence.cc @@ -9,6 +9,7 @@ namespace iguana { for(auto const& algo : m_sequence) algo->Start(banks); } + bool AlgorithmSequence::Run(hipo::banklist& banks) const { for(auto const& algo : m_sequence) { @@ -17,6 +18,7 @@ namespace iguana { } return true; } + void AlgorithmSequence::Stop() { for(auto const& algo : m_sequence) diff --git a/src/iguana/algorithms/AlgorithmSequence.h b/src/iguana/algorithms/AlgorithmSequence.h index 6a2275ac2..de3e00912 100644 --- a/src/iguana/algorithms/AlgorithmSequence.h +++ b/src/iguana/algorithms/AlgorithmSequence.h @@ -9,6 +9,10 @@ namespace iguana { /// The `Start`, `Run`, and `Stop` methods will sequentially call the corresponding algorithms' methods, /// in the order the algorithms were added to the sequence by `AlgorithmSequence::Add`. If an algorithm's /// `Run` function returns false, then `AlgorithmSequence`'s `Run` function will stop and return `false`. + /// + /// This algorithm requires the use of `hipo::banklist`; there are neither `Run` functions which take + /// individual `hipo::bank` parameters nor action functions. If you do not use `hipo::banklist`, you + /// should use individual algorithms instead of this sequencing algorithm. class AlgorithmSequence : public Algorithm { diff --git a/src/iguana/algorithms/clas12/CalorimeterLinker/Algorithm.cc b/src/iguana/algorithms/clas12/CalorimeterLinker/Algorithm.cc index b8c4ad336..401822d68 100644 --- a/src/iguana/algorithms/clas12/CalorimeterLinker/Algorithm.cc +++ b/src/iguana/algorithms/clas12/CalorimeterLinker/Algorithm.cc @@ -33,9 +33,17 @@ namespace iguana::clas12 { bool CalorimeterLinker::Run(hipo::banklist& banks) const { - auto& bank_particle = GetBank(banks, b_particle, "REC::Particle"); - auto& bank_calorimeter = GetBank(banks, b_calorimeter, "REC::Calorimeter"); - auto& bank_result = GetBank(banks, b_result, "REC::Particle::Calorimeter"); + return Run( + GetBank(banks, b_particle, "REC::Particle"), + GetBank(banks, b_calorimeter, "REC::Calorimeter"), + GetBank(banks, b_result, "REC::Particle::Calorimeter")); + } + + bool CalorimeterLinker::Run( + hipo::bank const& bank_particle, + hipo::bank const& bank_calorimeter, + hipo::bank& bank_result) const + { ShowBank(bank_particle, Logger::Header("INPUT PARTICLE BANK")); ShowBank(bank_calorimeter, Logger::Header("INPUT CALORIMETER BANK")); diff --git a/src/iguana/algorithms/clas12/CalorimeterLinker/Algorithm.h b/src/iguana/algorithms/clas12/CalorimeterLinker/Algorithm.h index b2bbe037f..6b55378c7 100644 --- a/src/iguana/algorithms/clas12/CalorimeterLinker/Algorithm.h +++ b/src/iguana/algorithms/clas12/CalorimeterLinker/Algorithm.h @@ -7,8 +7,6 @@ namespace iguana::clas12 { /// @brief_algo Link particle bank to bank `REC::Calorimeter` /// /// @begin_doc_algo{clas12::CalorimeterLinker | Creator} - /// @input_banks{REC::Particle, REC::Calorimeter} - /// @output_banks{%REC::Particle::Calorimeter} /// @end_doc /// /// This algorithm reads `REC::Calorimeter` and produces a new bank, `REC::Particle::Calorimeter`, @@ -28,6 +26,16 @@ namespace iguana::clas12 { bool Run(hipo::banklist& banks) const override; void Stop() override; + /// @run_function + /// @param [in] bank_particle `REC::Particle` + /// @param [in] bank_calorimeter `REC::Calorimeter` + /// @param [out] bank_result `REC::Particle::Calorimeter` + /// @run_function_returns_true + bool Run( + hipo::bank const& bank_particle, + hipo::bank const& bank_calorimeter, + hipo::bank& bank_result) const; + private: /// `hipo::banklist` indices diff --git a/src/iguana/algorithms/clas12/FTEnergyCorrection/Algorithm.cc b/src/iguana/algorithms/clas12/FTEnergyCorrection/Algorithm.cc index c8b881f7d..4b5c95309 100644 --- a/src/iguana/algorithms/clas12/FTEnergyCorrection/Algorithm.cc +++ b/src/iguana/algorithms/clas12/FTEnergyCorrection/Algorithm.cc @@ -8,8 +8,13 @@ namespace iguana::clas12 { electron_mass = particle::mass.at(particle::electron); } - bool FTEnergyCorrection::Run(hipo::banklist& banks) const { - auto& ftParticleBank = GetBank(banks, b_ft_particle, "RECFT::Particle"); + bool FTEnergyCorrection::Run(hipo::banklist& banks) const + { + return Run(GetBank(banks, b_ft_particle, "RECFT::Particle")); + } + + bool FTEnergyCorrection::Run(hipo::bank& ftParticleBank) const + { ShowBank(ftParticleBank, Logger::Header("INPUT FT PARTICLES")); for(auto const& row : ftParticleBank.getRowList()) { if(ftParticleBank.getInt("pid", row) == particle::PDG::electron) { diff --git a/src/iguana/algorithms/clas12/FTEnergyCorrection/Algorithm.h b/src/iguana/algorithms/clas12/FTEnergyCorrection/Algorithm.h index 04590da50..b653746b6 100644 --- a/src/iguana/algorithms/clas12/FTEnergyCorrection/Algorithm.h +++ b/src/iguana/algorithms/clas12/FTEnergyCorrection/Algorithm.h @@ -21,6 +21,11 @@ namespace iguana::clas12 { bool Run(hipo::banklist& banks) const override; void Stop() override; + /// @run_function + /// @param [in,out] ftParticleBank `RECFT::Particle`, which will have the correction applied + /// @run_function_returns_true + bool Run(hipo::bank& ftParticleBank) const; + /// @action_function{scalar transformer} /// Transformation function that returns 4-vector of electron with corrected energy for the Forward Tagger. /// Currently only validated for Fall 2018 outbending data. diff --git a/src/iguana/algorithms/clas12/FiducialFilter/Algorithm.cc b/src/iguana/algorithms/clas12/FiducialFilter/Algorithm.cc index 02cb37b26..259957dc8 100644 --- a/src/iguana/algorithms/clas12/FiducialFilter/Algorithm.cc +++ b/src/iguana/algorithms/clas12/FiducialFilter/Algorithm.cc @@ -8,11 +8,6 @@ namespace iguana::clas12 { void FiducialFilter::Start(hipo::banklist& banks) { ParseYAMLConfig(); - o_pass = GetOptionScalar("pass"); - if(o_pass!=1){ - m_log->Warn("FiducialFilter only contains fiducial cuts for pass1...we will default to using those..."); - o_pass = 1; - } o_pcal_electron_cut_level = ParseCutLevel(GetOptionScalar("pcal_electron_cut_level")); o_pcal_photon_cut_level = ParseCutLevel(GetOptionScalar("pcal_photon_cut_level")); o_enable_pcal_cuts = GetOptionScalar("enable_pcal_cuts") == 1; @@ -20,58 +15,63 @@ namespace iguana::clas12 { b_particle = GetBankIndex(banks, "REC::Particle"); b_config = GetBankIndex(banks, "RUN::config"); - if(o_pass == 1) { - b_traj = GetBankIndex(banks, "REC::Particle::Traj"); - b_cal = GetBankIndex(banks, "REC::Particle::Calorimeter"); - } + b_traj = GetBankIndex(banks, "REC::Particle::Traj"); + b_cal = GetBankIndex(banks, "REC::Particle::Calorimeter"); } ////////////////////////////////////////////////////////////////////////////////// - bool FiducialFilter::Run(hipo::banklist& banks) const { - - auto& particleBank = GetBank(banks, b_particle, "REC::Particle"); - auto& configBank = GetBank(banks, b_config, "RUN::config"); - auto torus = configBank.getFloat("torus", 0); + bool FiducialFilter::Run(hipo::banklist& banks) const + { + return Run( + GetBank(banks, b_particle, "REC::Particle"), + GetBank(banks, b_config, "RUN::config"), + GetBank(banks, b_traj, "REC::Particle::Traj"), + GetBank(banks, b_cal, "REC::Particle::Calorimeter")); + } + bool FiducialFilter::Run( + hipo::bank& particleBank, + hipo::bank const& configBank, + hipo::bank const& trajBank, + hipo::bank const& calBank) const + { ShowBank(particleBank, Logger::Header("INPUT PARTICLES")); - if(o_pass == 1) { - auto& trajBank = GetBank(banks, b_traj, "REC::Particle::Traj"); - auto& calBank = GetBank(banks, b_cal, "REC::Particle::Calorimeter"); - if(auto num_rows{particleBank.getRows()}; num_rows != trajBank.getRows() || num_rows != calBank.getRows()) { - m_log->Error("number of particle bank rows differs from 'REC::Particle::Traj' and/or 'REC::Particle::Calorimeter' rows; are you sure these input banks are being filled?"); - throw std::runtime_error("cannot proceed"); - } - particleBank.getMutableRowList().filter([this, torus, &trajBank, &calBank](hipo::bank& bank, int row) { - auto pid = bank.getInt("pid", row); - if(row >= 0 && row < calBank.getRows() && row < trajBank.getRows()) { - // call the action function - return FilterRgaPass1( - calBank.getInt("pcal_sector", row), - calBank.getFloat("pcal_lv", row), - calBank.getFloat("pcal_lw", row), - calBank.getByte("pcal_found", row) == 1, - trajBank.getInt("sector", row), - trajBank.getFloat("r1_x", row), - trajBank.getFloat("r1_y", row), - trajBank.getFloat("r1_z", row), - trajBank.getByte("r1_found", row) == 1, - trajBank.getFloat("r2_x", row), - trajBank.getFloat("r2_y", row), - trajBank.getFloat("r2_z", row), - trajBank.getByte("r2_found", row) == 1, - trajBank.getFloat("r3_x", row), - trajBank.getFloat("r3_y", row), - trajBank.getFloat("r3_z", row), - trajBank.getByte("r3_found", row) == 1, - torus, - pid); - } - else - throw std::runtime_error(fmt::format("FiducialFilter filter encountered bad row number {}", row)); - }); + if(auto num_rows{particleBank.getRows()}; num_rows != trajBank.getRows() || num_rows != calBank.getRows()) { + m_log->Error("number of particle bank rows differs from 'REC::Particle::Traj' and/or 'REC::Particle::Calorimeter' rows; are you sure these input banks are being filled?"); + throw std::runtime_error("cannot proceed"); } + auto torus = configBank.getFloat("torus", 0); + particleBank.getMutableRowList().filter([this, torus, &trajBank, &calBank](hipo::bank& bank, int row) { + auto pid = bank.getInt("pid", row); + if(row >= 0 && row < calBank.getRows() && row < trajBank.getRows()) { + // call the action function + return FilterRgaPass1( + calBank.getInt("pcal_sector", row), + calBank.getFloat("pcal_lv", row), + calBank.getFloat("pcal_lw", row), + calBank.getByte("pcal_found", row) == 1, + trajBank.getInt("sector", row), + trajBank.getFloat("r1_x", row), + trajBank.getFloat("r1_y", row), + trajBank.getFloat("r1_z", row), + trajBank.getByte("r1_found", row) == 1, + trajBank.getFloat("r2_x", row), + trajBank.getFloat("r2_y", row), + trajBank.getFloat("r2_z", row), + trajBank.getByte("r2_found", row) == 1, + trajBank.getFloat("r3_x", row), + trajBank.getFloat("r3_y", row), + trajBank.getFloat("r3_z", row), + trajBank.getByte("r3_found", row) == 1, + torus, + pid); + } + else + throw std::runtime_error(fmt::format("FiducialFilter filter encountered bad row number {}", row)); + } + ); ShowBank(particleBank, Logger::Header("OUTPUT PARTICLES")); return true; diff --git a/src/iguana/algorithms/clas12/FiducialFilter/Algorithm.h b/src/iguana/algorithms/clas12/FiducialFilter/Algorithm.h index fa7216343..5a60d4c29 100644 --- a/src/iguana/algorithms/clas12/FiducialFilter/Algorithm.h +++ b/src/iguana/algorithms/clas12/FiducialFilter/Algorithm.h @@ -9,8 +9,6 @@ namespace iguana::clas12 { /// Currently these are the "legacy" Pass 1 fiducial cuts tuned for Run Group A. /// /// @begin_doc_algo{clas12::FiducialFilter | Filter} - /// @input_banks{REC::Particle, RUN::config, and others (see below)} - /// @output_banks{REC::Particle} /// @end_doc /// /// The following **additional banks** are needed: @@ -43,6 +41,18 @@ namespace iguana::clas12 { bool Run(hipo::banklist& banks) const override; void Stop() override; + /// @run_function + /// @param [in,out] particleBank `REC::Particle`, which will be filtered + /// @param [in] configBank `RUN::config` + /// @param [in] trajBank `REC::Particle::Traj`, created by algorithm `clas12::TrajLinker` + /// @param [in] calBank `REC::Particle::Calorimeter`, created by algorithm `clas12::CalorimeterLinker` + /// @run_function_returns_true + bool Run( + hipo::bank& particleBank, + hipo::bank const& configBank, + hipo::bank const& trajBank, + hipo::bank const& calBank) const; + /// @action_function{scalar filter} top-level fiducial cut for RG-A Pass 1 /// @param pcal_sector PCAL sector /// @param pcal_lv PCAL lv @@ -169,7 +179,6 @@ namespace iguana::clas12 { hipo::banklist::size_type b_config; // options - int o_pass = 1; CutLevel o_pcal_electron_cut_level; CutLevel o_pcal_photon_cut_level; bool o_enable_pcal_cuts; diff --git a/src/iguana/algorithms/clas12/FiducialFilter/Config.yaml b/src/iguana/algorithms/clas12/FiducialFilter/Config.yaml index 46d3670fe..506166931 100644 --- a/src/iguana/algorithms/clas12/FiducialFilter/Config.yaml +++ b/src/iguana/algorithms/clas12/FiducialFilter/Config.yaml @@ -1,5 +1,4 @@ clas12::FiducialFilter: - pass: 1 # cut levels for PCAL homogeneous cuts; one of 'loose', 'medium', or 'tight' pcal_electron_cut_level: loose # for electrons and positrons From 438f671655311450b4598ed76926686354baabef Mon Sep 17 00:00:00 2001 From: Christopher Dilks Date: Fri, 12 Sep 2025 10:40:44 -0400 Subject: [PATCH 12/26] feat: done with `clas12::`'s `Run` functions --- doc/gen/Doxyfile.in | 2 -- .../clas12/CalorimeterLinker/Algorithm.h | 2 +- .../clas12/PhotonGBTFilter/Algorithm.cc | 17 +++++++++++------ .../clas12/PhotonGBTFilter/Algorithm.h | 12 ++++++++++-- .../algorithms/clas12/TrajLinker/Algorithm.cc | 13 ++++++++++--- .../algorithms/clas12/TrajLinker/Algorithm.h | 12 ++++++++++-- 6 files changed, 42 insertions(+), 16 deletions(-) diff --git a/doc/gen/Doxyfile.in b/doc/gen/Doxyfile.in index a6a71ee38..66f827ff9 100644 --- a/doc/gen/Doxyfile.in +++ b/doc/gen/Doxyfile.in @@ -287,8 +287,6 @@ ALIASES = # algorithm properties ALIASES += brief_algo="@brief **%Algorithm:** " ALIASES += begin_doc_algo{2|}="\par %Algorithm Name:^^   `%\1`\xrefitem algo \"Algorithm Type:\" \"List of all Algorithms\" \2 \par %Algorithm Inputs and Outputs:^^
NameTypeDescription
`\1``\2`\3
" -ALIASES += input_banks{1}="" -ALIASES += output_banks{1}="" ALIASES += end_doc="
**Input Banks**`\1`
**Output Banks**`\1`
" # configuration options ALIASES += begin_doc_config{1}="\par Configuration Options:^^YAML configuration, which includes the default option values:^^@include \1/Config.yaml ^^Table of options and descriptions:^^" diff --git a/src/iguana/algorithms/clas12/CalorimeterLinker/Algorithm.h b/src/iguana/algorithms/clas12/CalorimeterLinker/Algorithm.h index 6b55378c7..ef2863dfe 100644 --- a/src/iguana/algorithms/clas12/CalorimeterLinker/Algorithm.h +++ b/src/iguana/algorithms/clas12/CalorimeterLinker/Algorithm.h @@ -29,7 +29,7 @@ namespace iguana::clas12 { /// @run_function /// @param [in] bank_particle `REC::Particle` /// @param [in] bank_calorimeter `REC::Calorimeter` - /// @param [out] bank_result `REC::Particle::Calorimeter` + /// @param [out] bank_result `REC::Particle::Calorimeter`, which will be created /// @run_function_returns_true bool Run( hipo::bank const& bank_particle, diff --git a/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.cc b/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.cc index b44b4dfcd..a201d4e91 100644 --- a/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.cc +++ b/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.cc @@ -43,14 +43,19 @@ namespace iguana::clas12 { o_threshold = GetOptionScalar("threshold"); } - - bool PhotonGBTFilter::Run(hipo::banklist& banks) const { - - auto& particleBank = GetBank(banks, b_particle, "REC::Particle"); - auto& caloBank = GetBank(banks, b_calorimeter, "REC::Calorimeter"); - auto& configBank = GetBank(banks,b_config,"RUN::config"); + return Run( + GetBank(banks, b_particle, "REC::Particle"), + GetBank(banks, b_calorimeter, "REC::Calorimeter"), + GetBank(banks,b_config,"RUN::config")); + } + + bool PhotonGBTFilter::Run( + hipo::bank& particleBank, + hipo::bank const& caloBank, + hipo::bank const& configBank) const + { int runnum = configBank.getInt("run",0); // Get CaloMap for the event diff --git a/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.h b/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.h index dc4db7456..ea972f39a 100644 --- a/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.h +++ b/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.h @@ -13,8 +13,6 @@ namespace iguana::clas12 { /// For each photon (labeled the photon of interest or POI), we obtain its intrinsic features (energy, angle, pcal edep, etc.) and features corresponding to its nearest neighbors (angle of proximity, energy difference, etc.). This requires the reading of both the REC::Particle and REC::Calorimeter banks. An input std::vector is produced and passed to the pretrained GBT models, which yield a classification score between 0 and 1. An option variable `threshold` then determines the minimum photon `p-value` to survive the cut. /// /// @begin_doc_algo{clas12::PhotonGBTFilter | Filter} - /// @input_banks{REC::Particle, REC::Calorimeter, RUN::config} - /// @output_banks{REC::Particle} /// @end_doc /// /// @begin_doc_config{clas12/PhotonGBTFilter} @@ -32,6 +30,16 @@ namespace iguana::clas12 { bool Run(hipo::banklist& banks) const override; void Stop() override; + /// @run_function + /// @param [in,out] particleBank `REC::Particle`, which will be filtered + /// @param [in] caloBank `REC::Calorimeter` + /// @param [in] configBank `RUN::config` + /// @run_function_returns_true + bool Run( + hipo::bank& particleBank, + hipo::bank const& caloBank, + hipo::bank const& configBank) const; + /// Applies forward detector cut using REC::Particle Theta /// @param theta lab angle of the particle with respect to the beam direction (radians) /// @returns `true` if the particle's theta is within the forward detector coverage, `false` otherwise diff --git a/src/iguana/algorithms/clas12/TrajLinker/Algorithm.cc b/src/iguana/algorithms/clas12/TrajLinker/Algorithm.cc index a624ec15f..9815e525a 100644 --- a/src/iguana/algorithms/clas12/TrajLinker/Algorithm.cc +++ b/src/iguana/algorithms/clas12/TrajLinker/Algorithm.cc @@ -28,10 +28,17 @@ namespace iguana::clas12 { bool TrajLinker::Run(hipo::banklist& banks) const { - auto& bank_particle = GetBank(banks, b_particle, "REC::Particle"); - auto& bank_traj = GetBank(banks, b_traj, "REC::Traj"); - auto& bank_result = GetBank(banks, b_result, "REC::Particle::Traj"); + return Run( + GetBank(banks, b_particle, "REC::Particle"), + GetBank(banks, b_traj, "REC::Traj"), + GetBank(banks, b_result, "REC::Particle::Traj")); + } + bool TrajLinker::Run( + hipo::bank const& bank_particle, + hipo::bank const& bank_traj, + hipo::bank& bank_result) const + { ShowBank(bank_particle, Logger::Header("INPUT PARTICLE BANK")); ShowBank(bank_traj, Logger::Header("INPUT TRAJECTORY BANK")); diff --git a/src/iguana/algorithms/clas12/TrajLinker/Algorithm.h b/src/iguana/algorithms/clas12/TrajLinker/Algorithm.h index aff26134d..5ecb47a0e 100644 --- a/src/iguana/algorithms/clas12/TrajLinker/Algorithm.h +++ b/src/iguana/algorithms/clas12/TrajLinker/Algorithm.h @@ -7,8 +7,6 @@ namespace iguana::clas12 { /// @brief_algo Link particle bank to bank `REC::Traj` /// /// @begin_doc_algo{clas12::TrajLinker | Creator} - /// @input_banks{REC::Particle, REC::Traj} - /// @output_banks{%REC::Particle::Traj} /// @end_doc /// /// This algorithm reads `REC::Traj` and produces a new bank, `REC::Particle::Traj`, @@ -28,6 +26,16 @@ namespace iguana::clas12 { bool Run(hipo::banklist& banks) const override; void Stop() override; + /// @run_function + /// @param bank_particle [in] `REC::Particle` + /// @param bank_traj [in] `REC::Traj` + /// @param bank_result [out] `REC::Particle::Traj`, which will be created + /// @run_function_returns_true + bool Run( + hipo::bank const& bank_particle, + hipo::bank const& bank_traj, + hipo::bank& bank_result) const; + /// @returns the DC sector given (x,y,z), or `-1` if failed /// @param x x-position /// @param y y-position From c3ff3b5805402b13e961567a7add35d970141878 Mon Sep 17 00:00:00 2001 From: Christopher Dilks Date: Fri, 12 Sep 2025 20:10:43 -0400 Subject: [PATCH 13/26] feat: done with all the `Run` functions --- .../algorithms/clas12/TrajLinker/Algorithm.h | 6 +++--- .../example/ExampleAlgorithm/Algorithm.cc | 17 +++++++++++++---- .../example/ExampleAlgorithm/Algorithm.h | 19 +++++++++++++++++++ .../physics/Depolarization/Algorithm.cc | 10 ++++++++-- .../physics/Depolarization/Algorithm.h | 10 ++++++++-- .../physics/DihadronKinematics/Algorithm.cc | 14 +++++++++++--- .../physics/DihadronKinematics/Algorithm.h | 12 ++++++++++-- .../physics/InclusiveKinematics/Algorithm.h | 3 +-- .../SingleHadronKinematics/Algorithm.cc | 14 +++++++++++--- .../SingleHadronKinematics/Algorithm.h | 12 ++++++++++-- 10 files changed, 94 insertions(+), 23 deletions(-) diff --git a/src/iguana/algorithms/clas12/TrajLinker/Algorithm.h b/src/iguana/algorithms/clas12/TrajLinker/Algorithm.h index 5ecb47a0e..6628e6a4d 100644 --- a/src/iguana/algorithms/clas12/TrajLinker/Algorithm.h +++ b/src/iguana/algorithms/clas12/TrajLinker/Algorithm.h @@ -27,9 +27,9 @@ namespace iguana::clas12 { void Stop() override; /// @run_function - /// @param bank_particle [in] `REC::Particle` - /// @param bank_traj [in] `REC::Traj` - /// @param bank_result [out] `REC::Particle::Traj`, which will be created + /// @param [in] bank_particle `REC::Particle` + /// @param [in] bank_traj `REC::Traj` + /// @param [out] bank_result `REC::Particle::Traj`, which will be created /// @run_function_returns_true bool Run( hipo::bank const& bank_particle, diff --git a/src/iguana/algorithms/example/ExampleAlgorithm/Algorithm.cc b/src/iguana/algorithms/example/ExampleAlgorithm/Algorithm.cc index 744448c54..71f0cde6f 100644 --- a/src/iguana/algorithms/example/ExampleAlgorithm/Algorithm.cc +++ b/src/iguana/algorithms/example/ExampleAlgorithm/Algorithm.cc @@ -52,19 +52,28 @@ namespace iguana::example { // ############################################################################ - // # define `ExampleAlgorithm::Run()` - // # - this overrides the virtual function `Algorithm::Run` + // # define `ExampleAlgorithm::Run` functions // # - note that this method must be _thread safe_, for example, you cannot modify // # class instance objects // # - try to avoid expensive operations here; instead, put them in the `Start` method // # if it is reasonable to do so + // # - the function's `bool` return value can be used as an event-level filter + // # - the `Run` function that acts on `hipo::banklist` should just call the `Run` + // # function that acts on `hipo::bank` objects; let's define it first // ############################################################################ bool ExampleAlgorithm::Run(hipo::banklist& banks) const { // ############################################################################ - // # get the banks; here we just need `REC::Particle` + // # use `GetBank` to get the banks; here we just need `REC::Particle` // ############################################################################ - auto& particleBank = GetBank(banks, b_particle, "REC::Particle"); + return Run(GetBank(banks, b_particle, "REC::Particle")); + } + + // ############################################################################ + // # here is the `Run` function which acts on `hipo::bank` objects + // ############################################################################ + bool ExampleAlgorithm::Run(hipo::bank& particleBank) const + { // ############################################################################ // # dump the bank // # - this will only happen if the log level for this algorithm is set low enough diff --git a/src/iguana/algorithms/example/ExampleAlgorithm/Algorithm.h b/src/iguana/algorithms/example/ExampleAlgorithm/Algorithm.h index 29dc1f440..176be3884 100644 --- a/src/iguana/algorithms/example/ExampleAlgorithm/Algorithm.h +++ b/src/iguana/algorithms/example/ExampleAlgorithm/Algorithm.h @@ -66,6 +66,25 @@ namespace iguana::example { bool Run(hipo::banklist& banks) const override; void Stop() override; + // ############################################################################ + // # define an additional `Run` function which takes `hipo::bank` parameters + // # - the parameters should be lvalue references, i.e., `hipo::bank&`, to avoid copying the banks + // # - if a bank is ONLY read, and not modified, you should use `const`, i.e., `hipo::bank const&` + // # - in this example, `particleBank` will be modified, so we use `hipo::bank&` + // # - be sure the function itself is also marked `const` + // # - you'll also need to write Doxygen docstrings for this function + // # - use `@run_function`, so the documentation understands this is a `Run` function + // # - use `@param [in]` for a bank that is only read (type should be `hipo::bank const&`) + // # - use `@param [out]` for a bank that is newly created (type should be `hipo::bank&`) + // # - use `@param [in,out]` for a bank that is read and mutated (type should be `hipo::bank&`) + // # - use `@run_function_returns_true` if the function does not use the `bool` return value, otherwise + // # use `@returns` and explain why the return value could be `false` + // ############################################################################ + /// @run_function + /// @param [in,out] particleBank `REC::Particle` bank + /// @run_function_returns_true + bool Run(hipo::bank& particleBank) const; + // ############################################################################ // # additional public functions go here // # - typically these are "action functions", which expose the primary operation of an algorithm diff --git a/src/iguana/algorithms/physics/Depolarization/Algorithm.cc b/src/iguana/algorithms/physics/Depolarization/Algorithm.cc index e13e4c58d..4597e272a 100644 --- a/src/iguana/algorithms/physics/Depolarization/Algorithm.cc +++ b/src/iguana/algorithms/physics/Depolarization/Algorithm.cc @@ -22,9 +22,15 @@ namespace iguana::physics { bool Depolarization::Run(hipo::banklist& banks) const { - auto& inc_kin_bank = GetBank(banks, b_inc_kin, "physics::InclusiveKinematics"); - auto& result_bank = GetBank(banks, b_result, GetClassName()); + return Run( + GetBank(banks, b_inc_kin, "physics::InclusiveKinematics"), + GetBank(banks, b_result, GetClassName())); + } + bool Depolarization::Run( + hipo::bank const& inc_kin_bank, + hipo::bank& result_bank) const + { ShowBank(inc_kin_bank, Logger::Header("INPUT INCLUSIVE KINEMATICS")); // set `result_bank` rows and rowlist to match those of `inc_kin_bank` diff --git a/src/iguana/algorithms/physics/Depolarization/Algorithm.h b/src/iguana/algorithms/physics/Depolarization/Algorithm.h index 254ea5e7f..607ee022e 100644 --- a/src/iguana/algorithms/physics/Depolarization/Algorithm.h +++ b/src/iguana/algorithms/physics/Depolarization/Algorithm.h @@ -11,8 +11,6 @@ namespace iguana::physics { /// - https://arxiv.org/pdf/1408.5721 /// /// @begin_doc_algo{physics::Depolarization | Creator} - /// @input_banks{%physics::InclusiveKinematics} - /// @output_banks{%physics::Depolarization} /// @end_doc /// /// @creator_note @@ -27,6 +25,14 @@ namespace iguana::physics { bool Run(hipo::banklist& banks) const override; void Stop() override; + /// @run_function + /// @param [in] inc_kin_bank `%physics::InclusiveKinematics`, produced by the `physics::InclusiveKinematics` algorithm + /// @param [out] result_bank `%physics::Depolarization`, which will be created + /// @run_function_returns_true + bool Run( + hipo::bank const& inc_kin_bank, + hipo::bank& result_bank) const; + /// @action_function{scalar creator} compute depolarization factors /// @param Q2 @latex{Q^2}, from `iguana::physics::InclusiveKinematics` /// @param x Bjorken-@latex{x}, from `iguana::physics::InclusiveKinematics` diff --git a/src/iguana/algorithms/physics/DihadronKinematics/Algorithm.cc b/src/iguana/algorithms/physics/DihadronKinematics/Algorithm.cc index 7cebeb129..fda4f8735 100644 --- a/src/iguana/algorithms/physics/DihadronKinematics/Algorithm.cc +++ b/src/iguana/algorithms/physics/DihadronKinematics/Algorithm.cc @@ -52,9 +52,17 @@ namespace iguana::physics { bool DihadronKinematics::Run(hipo::banklist& banks) const { - auto& particle_bank = GetBank(banks, b_particle, "REC::Particle"); - auto& inc_kin_bank = GetBank(banks, b_inc_kin, "physics::InclusiveKinematics"); - auto& result_bank = GetBank(banks, b_result, GetClassName()); + return Run( + GetBank(banks, b_particle, "REC::Particle"), + GetBank(banks, b_inc_kin, "physics::InclusiveKinematics"), + GetBank(banks, b_result, GetClassName())); + } + + bool DihadronKinematics::Run( + hipo::bank const& particle_bank, + hipo::bank const& inc_kin_bank, + hipo::bank& result_bank) const + { ShowBank(particle_bank, Logger::Header("INPUT PARTICLES")); if(particle_bank.getRowList().empty() || inc_kin_bank.getRowList().empty()) { diff --git a/src/iguana/algorithms/physics/DihadronKinematics/Algorithm.h b/src/iguana/algorithms/physics/DihadronKinematics/Algorithm.h index 0b6dc929e..afe648ef7 100644 --- a/src/iguana/algorithms/physics/DihadronKinematics/Algorithm.h +++ b/src/iguana/algorithms/physics/DihadronKinematics/Algorithm.h @@ -10,8 +10,6 @@ namespace iguana::physics { /// @brief_algo Calculate semi-inclusive dihadron kinematic quantities defined in `iguana::physics::DihadronKinematicsVars` /// /// @begin_doc_algo{physics::DihadronKinematics | Creator} - /// @input_banks{REC::Particle, %physics::InclusiveKinematics} - /// @output_banks{%physics::DihadronKinematics} /// @end_doc /// /// @begin_doc_config{physics/DihadronKinematics} @@ -49,6 +47,16 @@ namespace iguana::physics { bool Run(hipo::banklist& banks) const override; void Stop() override; + /// @run_function + /// @param [in] particle_bank `REC::Particle` + /// @param [in] inc_kin_bank `%physics::InclusiveKinematics`, produced by the `physics::InclusiveKinematics` algorithm + /// @param [out] result_bank `%physics::DihadronKinematics`, which will be created + /// @run_function_returns_true + bool Run( + hipo::bank const& particle_bank, + hipo::bank const& inc_kin_bank, + hipo::bank& result_bank) const; + /// @brief form dihadrons by pairing hadrons /// @param particle_bank the particle bank (`REC::Particle`) /// @returns a list of pairs of hadron rows diff --git a/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h b/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h index d553ffe14..070c7127a 100644 --- a/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h +++ b/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h @@ -10,7 +10,6 @@ namespace iguana::physics { /// @brief_algo Calculate inclusive kinematics quantities /// /// @begin_doc_algo{physics::InclusiveKinematics | Creator} - /// /// @end_doc /// /// @begin_doc_config{physics/InclusiveKinematics} @@ -36,7 +35,7 @@ namespace iguana::physics { /// @run_function /// @param [in] particle_bank `REC::Particle` /// @param [in] config_bank `RUN::config` - /// @param [out] result_bank `%physics::InclusiveKinematics` + /// @param [out] result_bank `%physics::InclusiveKinematics`, which will be created /// @returns `true` if the kinematics were calculated, _e.g._, if the calculations are performed using /// the scattered lepton, and no scattered lepton was found, `false` will be returned bool Run( diff --git a/src/iguana/algorithms/physics/SingleHadronKinematics/Algorithm.cc b/src/iguana/algorithms/physics/SingleHadronKinematics/Algorithm.cc index b0297c2ef..96ec027f0 100644 --- a/src/iguana/algorithms/physics/SingleHadronKinematics/Algorithm.cc +++ b/src/iguana/algorithms/physics/SingleHadronKinematics/Algorithm.cc @@ -36,9 +36,17 @@ namespace iguana::physics { bool SingleHadronKinematics::Run(hipo::banklist& banks) const { - auto& particle_bank = GetBank(banks, b_particle, "REC::Particle"); - auto& inc_kin_bank = GetBank(banks, b_inc_kin, "physics::InclusiveKinematics"); - auto& result_bank = GetBank(banks, b_result, GetClassName()); + return Run( + GetBank(banks, b_particle, "REC::Particle"), + GetBank(banks, b_inc_kin, "physics::InclusiveKinematics"), + GetBank(banks, b_result, GetClassName())); + } + + bool SingleHadronKinematics::Run( + hipo::bank const& particle_bank, + hipo::bank const& inc_kin_bank, + hipo::bank& result_bank) const + { ShowBank(particle_bank, Logger::Header("INPUT PARTICLES")); if(particle_bank.getRowList().empty() || inc_kin_bank.getRowList().empty()) { diff --git a/src/iguana/algorithms/physics/SingleHadronKinematics/Algorithm.h b/src/iguana/algorithms/physics/SingleHadronKinematics/Algorithm.h index 3451309d4..4e18812f6 100644 --- a/src/iguana/algorithms/physics/SingleHadronKinematics/Algorithm.h +++ b/src/iguana/algorithms/physics/SingleHadronKinematics/Algorithm.h @@ -9,8 +9,6 @@ namespace iguana::physics { /// @brief_algo Calculate semi-inclusive hadron kinematic quantities /// /// @begin_doc_algo{physics::SingleHadronKinematics | Creator} - /// @input_banks{REC::Particle, %physics::InclusiveKinematics} - /// @output_banks{%physics::SingleHadronKinematics} /// @end_doc /// /// @begin_doc_config{physics/SingleHadronKinematics} @@ -38,6 +36,16 @@ namespace iguana::physics { bool Run(hipo::banklist& banks) const override; void Stop() override; + /// @run_function + /// @param [in] particle_bank `REC::Particle` + /// @param [in] inc_kin_bank `%physics::InclusiveKinematics`, produced by the `physics::InclusiveKinematics` algorithm + /// @param [out] result_bank `%physics::SingleHadronKinematics`, which will be created + /// @run_function_returns_true + bool Run( + hipo::bank const& particle_bank, + hipo::bank const& inc_kin_bank, + hipo::bank& result_bank) const; + private: // banklist indices From 94eeb73d95fff78ce9caf0a82e3fdfddf3f167fb Mon Sep 17 00:00:00 2001 From: Christopher Dilks Date: Wed, 17 Sep 2025 19:56:31 -0400 Subject: [PATCH 14/26] doc: update algorithm docstrings --- doc/gen/Doxyfile.in | 11 ++++++----- .../algorithms/clas12/CalorimeterLinker/Algorithm.h | 8 ++------ .../algorithms/clas12/EventBuilderFilter/Algorithm.h | 7 ++----- .../algorithms/clas12/FTEnergyCorrection/Algorithm.h | 8 ++------ .../algorithms/clas12/FiducialFilter/Algorithm.h | 6 ++---- .../algorithms/clas12/MomentumCorrection/Algorithm.h | 10 ++-------- .../algorithms/clas12/PhotonGBTFilter/Algorithm.h | 6 ++---- src/iguana/algorithms/clas12/SectorFinder/Algorithm.h | 9 ++------- src/iguana/algorithms/clas12/TrajLinker/Algorithm.h | 8 ++------ .../algorithms/clas12/ZVertexFilter/Algorithm.h | 7 ++----- .../algorithms/example/ExampleAlgorithm/Algorithm.h | 8 ++------ .../algorithms/physics/Depolarization/Algorithm.h | 8 ++------ .../algorithms/physics/DihadronKinematics/Algorithm.h | 8 ++------ .../physics/InclusiveKinematics/Algorithm.h | 9 ++------- .../physics/SingleHadronKinematics/Algorithm.h | 8 ++------ 15 files changed, 34 insertions(+), 87 deletions(-) diff --git a/doc/gen/Doxyfile.in b/doc/gen/Doxyfile.in index 66f827ff9..53aabe38a 100644 --- a/doc/gen/Doxyfile.in +++ b/doc/gen/Doxyfile.in @@ -285,15 +285,17 @@ TAB_SIZE = 4 ALIASES = # algorithm properties -ALIASES += brief_algo="@brief **%Algorithm:** " -ALIASES += begin_doc_algo{2|}="\par %Algorithm Name:^^   `%\1`\xrefitem algo \"Algorithm Type:\" \"List of all Algorithms\" \2 \par %Algorithm Inputs and Outputs:^^
NameTypeDescription
see this algorithm's Run function(s) for the input and output bank names
" -ALIASES += end_doc="
" +ALIASES += algo_brief{1}="@brief **%Algorithm:** \1^^@xrefitem algo \"\" \"\" \1^^" +ALIASES += algo_type_filter="@par Type: Filter^^This algorithm will filter input bank(s).^^" +ALIASES += algo_type_transformer="@par Type: Transformer^^This algorithm will change values within input bank(s).^^" +ALIASES += algo_type_creator="@par Type: Creator^^This algorithm creates new bank(s) and its definition is found within the \link src/iguana/bankdefs/iguana.json **Iguana Bank Definitions JSON File** \endlink
  • For guidance on how to read this JSON file, [see documentation on created banks](#mainpageCreatedBanks).
  • See also the return value type of this algorithm's action functions, which may be `struct`s with the same set of variables as the created bank.
" # configuration options ALIASES += begin_doc_config{1}="\par Configuration Options:^^YAML configuration, which includes the default option values:^^@include \1/Config.yaml ^^Table of options and descriptions:^^" ALIASES += config_param{3|}="" +ALIASES += end_doc="
NameTypeDescription
`\1``\2`\3
^^" # run functions ALIASES += run_function="@brief **Run Function**" -ALIASES += run_function_returns_true{}="@returns `true`, _i.e._, this `Run` function does not provide an event-level filter" +ALIASES += run_function_returns_true="@returns `true`, _i.e._, this `Run` function does not provide an event-level filter" # action functions ALIASES += action_function{1}="\xrefitem action \"Function Type\" \"List of all Action Functions\" \1 \brief **Action Function:** " ALIASES += when_to_call{1}="@note This function should be called **\1**" @@ -308,7 +310,6 @@ ALIASES += doxygen_on="@endcond" ALIASES += latex{1}="@f$\1@f$" # misc ALIASES += spacer="     " -ALIASES += creator_note="This algorithm creates a new bank and its definition is found within the \link src/iguana/bankdefs/iguana.json **Iguana Bank Definitions JSON File** \endlink
  • For guidance on how to read this JSON file, [see documentation on created banks](#mainpageCreatedBanks).
  • See also the return value type of this algorithm's action functions, which may be `struct`s with the same set of variables as the created bank.
" # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For diff --git a/src/iguana/algorithms/clas12/CalorimeterLinker/Algorithm.h b/src/iguana/algorithms/clas12/CalorimeterLinker/Algorithm.h index ef2863dfe..0c1bbcd60 100644 --- a/src/iguana/algorithms/clas12/CalorimeterLinker/Algorithm.h +++ b/src/iguana/algorithms/clas12/CalorimeterLinker/Algorithm.h @@ -4,17 +4,13 @@ namespace iguana::clas12 { - /// @brief_algo Link particle bank to bank `REC::Calorimeter` - /// - /// @begin_doc_algo{clas12::CalorimeterLinker | Creator} - /// @end_doc + /// @algo_brief{Link particle bank to bank `REC::Calorimeter`} + /// @algo_type_creator /// /// This algorithm reads `REC::Calorimeter` and produces a new bank, `REC::Particle::Calorimeter`, /// to make it easier to access commonly used `REC::Calorimeter` information for each particle. /// /// If this algorithm does not provide information you need, ask the maintainers or open a pull request. - /// - /// @creator_note class CalorimeterLinker : public Algorithm { diff --git a/src/iguana/algorithms/clas12/EventBuilderFilter/Algorithm.h b/src/iguana/algorithms/clas12/EventBuilderFilter/Algorithm.h index e6e66fc47..30fb4f245 100644 --- a/src/iguana/algorithms/clas12/EventBuilderFilter/Algorithm.h +++ b/src/iguana/algorithms/clas12/EventBuilderFilter/Algorithm.h @@ -4,11 +4,8 @@ namespace iguana::clas12 { - /// @brief_algo Filter the `REC::Particle` (or similar) bank by PID from the Event Builder - /// - /// @begin_doc_algo{clas12::EventBuilderFilter | Filter} - /// see this algorithm's Run function(s) for the input and output bank names - /// @end_doc + /// @algo_brief{Filter the `REC::Particle` (or similar) bank by PID from the Event Builder} + /// @algo_type_filter /// /// @begin_doc_config{clas12/EventBuilderFilter} /// @config_param{pids | list[int] | list of PDG codes to filter} diff --git a/src/iguana/algorithms/clas12/FTEnergyCorrection/Algorithm.h b/src/iguana/algorithms/clas12/FTEnergyCorrection/Algorithm.h index b653746b6..e70d40def 100644 --- a/src/iguana/algorithms/clas12/FTEnergyCorrection/Algorithm.h +++ b/src/iguana/algorithms/clas12/FTEnergyCorrection/Algorithm.h @@ -5,12 +5,8 @@ namespace iguana::clas12 { - /// @brief_algo Forward Tagger energy correction - /// - /// @begin_doc_algo{clas12::FTEnergyCorrection | Transformer} - /// @input_banks{RECFT::Particle} - /// @output_banks{RECFT::Particle} - /// @end_doc + /// @algo_brief{Forward Tagger energy correction} + /// @algo_type_transformer class FTEnergyCorrection : public Algorithm { DEFINE_IGUANA_ALGORITHM(FTEnergyCorrection, clas12::FTEnergyCorrection) diff --git a/src/iguana/algorithms/clas12/FiducialFilter/Algorithm.h b/src/iguana/algorithms/clas12/FiducialFilter/Algorithm.h index 5a60d4c29..6cf940033 100644 --- a/src/iguana/algorithms/clas12/FiducialFilter/Algorithm.h +++ b/src/iguana/algorithms/clas12/FiducialFilter/Algorithm.h @@ -4,13 +4,11 @@ namespace iguana::clas12 { - /// @brief_algo Filter the `REC::Particle` bank by applying DC (drift chamber) and ECAL (electromagnetic calorimeter) fiducial cuts + /// @algo_brief{Filter the `REC::Particle` bank by applying DC (drift chamber) and ECAL (electromagnetic calorimeter) fiducial cuts} + /// @algo_type_filter /// /// Currently these are the "legacy" Pass 1 fiducial cuts tuned for Run Group A. /// - /// @begin_doc_algo{clas12::FiducialFilter | Filter} - /// @end_doc - /// /// The following **additional banks** are needed: /// - if configuration option `pass` is `1`: /// - `REC::Particle::Traj`, created by algorithm `iguana::clas12::TrajLinker` diff --git a/src/iguana/algorithms/clas12/MomentumCorrection/Algorithm.h b/src/iguana/algorithms/clas12/MomentumCorrection/Algorithm.h index bb13aeba5..f43c416de 100644 --- a/src/iguana/algorithms/clas12/MomentumCorrection/Algorithm.h +++ b/src/iguana/algorithms/clas12/MomentumCorrection/Algorithm.h @@ -5,15 +5,9 @@ namespace iguana::clas12 { - /// @brief_algo Momentum Corrections - /// + /// @algo_brief{Momentum Corrections} + /// @algo_type_transformer /// Adapted from - /// - /// @begin_doc_algo{clas12::MomentumCorrection | Transformer} - /// see this algorithm's Run function(s) for the input and output bank names - /// @input_banks{RUN::config, REC::Particle, REC::Particle::Sector} - /// @output_banks{REC::Particle} - /// @end_doc class MomentumCorrection : public Algorithm { diff --git a/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.h b/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.h index ea972f39a..9f807c0b6 100644 --- a/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.h +++ b/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.h @@ -8,13 +8,11 @@ namespace iguana::clas12 { /// - /// @brief_algo Filter the `REC::Particle` photons using pretrained GBT models + /// @algo_brief{Filter the `REC::Particle` photons using pretrained GBT models} + /// @algo_type_filter /// /// For each photon (labeled the photon of interest or POI), we obtain its intrinsic features (energy, angle, pcal edep, etc.) and features corresponding to its nearest neighbors (angle of proximity, energy difference, etc.). This requires the reading of both the REC::Particle and REC::Calorimeter banks. An input std::vector is produced and passed to the pretrained GBT models, which yield a classification score between 0 and 1. An option variable `threshold` then determines the minimum photon `p-value` to survive the cut. /// - /// @begin_doc_algo{clas12::PhotonGBTFilter | Filter} - /// @end_doc - /// /// @begin_doc_config{clas12/PhotonGBTFilter} /// @config_param{pass | int | cook type} /// @config_param{threshold | double | minimum value to qualify a photon as "true"} diff --git a/src/iguana/algorithms/clas12/SectorFinder/Algorithm.h b/src/iguana/algorithms/clas12/SectorFinder/Algorithm.h index 2aaaad256..7c9a00b93 100644 --- a/src/iguana/algorithms/clas12/SectorFinder/Algorithm.h +++ b/src/iguana/algorithms/clas12/SectorFinder/Algorithm.h @@ -5,11 +5,8 @@ namespace iguana::clas12 { - /// @brief_algo Find the sector for all rows in `REC::Particle` - /// - /// @begin_doc_algo{clas12::SectorFinder | Creator} - /// see this algorithm's Run function(s) for the input and output bank names - /// @end_doc + /// @algo_brief{Find the sector for all rows in `REC::Particle`} + /// @algo_type_creator /// /// @begin_doc_config{clas12/SectorFinder} /// @config_param{bank_charged | string | if not `default`, use this bank for sector finding of charged particles} @@ -27,8 +24,6 @@ namespace iguana::clas12 { /// The action function ::GetStandardSector identifies the sector(s) using these banks in a priority order, whereas /// the action function ::GetSector uses a single bank's data. /// Note: rows that have been filtered out of `REC::Particle` will still have their sectors determined. - /// - /// @creator_note class SectorFinder : public Algorithm { diff --git a/src/iguana/algorithms/clas12/TrajLinker/Algorithm.h b/src/iguana/algorithms/clas12/TrajLinker/Algorithm.h index 6628e6a4d..48430cc11 100644 --- a/src/iguana/algorithms/clas12/TrajLinker/Algorithm.h +++ b/src/iguana/algorithms/clas12/TrajLinker/Algorithm.h @@ -4,17 +4,13 @@ namespace iguana::clas12 { - /// @brief_algo Link particle bank to bank `REC::Traj` - /// - /// @begin_doc_algo{clas12::TrajLinker | Creator} - /// @end_doc + /// @algo_brief{Link particle bank to bank `REC::Traj`} + /// @algo_type_creator /// /// This algorithm reads `REC::Traj` and produces a new bank, `REC::Particle::Traj`, /// to make it easier to access commonly used `REC::Traj` information for each particle. /// /// If this algorithm does not provide information you need, ask the maintainers or open a pull request. - /// - /// @creator_note class TrajLinker : public Algorithm { diff --git a/src/iguana/algorithms/clas12/ZVertexFilter/Algorithm.h b/src/iguana/algorithms/clas12/ZVertexFilter/Algorithm.h index f7e470270..6e8dcc9fc 100644 --- a/src/iguana/algorithms/clas12/ZVertexFilter/Algorithm.h +++ b/src/iguana/algorithms/clas12/ZVertexFilter/Algorithm.h @@ -5,11 +5,8 @@ namespace iguana::clas12 { - /// @brief_algo Filter the `REC::Particle` (or similar) bank by cutting on Z Vertex - /// - /// @begin_doc_algo{clas12::ZVertexFilter | Filter} - /// see this algorithm's Run function(s) for the input and output bank names - /// @end_doc + /// @algo_brief{Filter the `REC::Particle` (or similar) bank by cutting on Z Vertex} + /// @algo_type_filter /// /// @begin_doc_config{clas12/ZVertexFilter} /// @config_param{electron_vz | list[double] | lower and upper electron @f$z@f$-vertex cuts; run-range dependent; cuts are not applied to FT electrons (FD and CD only)} diff --git a/src/iguana/algorithms/example/ExampleAlgorithm/Algorithm.h b/src/iguana/algorithms/example/ExampleAlgorithm/Algorithm.h index 176be3884..d3756073e 100644 --- a/src/iguana/algorithms/example/ExampleAlgorithm/Algorithm.h +++ b/src/iguana/algorithms/example/ExampleAlgorithm/Algorithm.h @@ -26,15 +26,11 @@ namespace iguana::example { // # - see Doxygen documentation for more details, or see other algorithms // ############################################################################ /// - /// @brief_algo This is a template algorithm, used as an example showing how to write an algorithm. + /// @algo_brief{This is a template algorithm, used as an example showing how to write an algorithm.} + /// @algo_type_filter /// /// Provide a more detailed description of your algorithm here. /// - /// @begin_doc_algo{example::ExampleAlgorithm | Filter} - /// @input_banks{REC::Particle} - /// @output_banks{REC::Particle} - /// @end_doc - /// /// @begin_doc_config{example/ExampleAlgorithm} /// @config_param{exampleInt | int | an example `integer` configuration parameter} /// @config_param{exampleDouble | double | an example `double` configuration parameter} diff --git a/src/iguana/algorithms/physics/Depolarization/Algorithm.h b/src/iguana/algorithms/physics/Depolarization/Algorithm.h index 607ee022e..2f1205b0e 100644 --- a/src/iguana/algorithms/physics/Depolarization/Algorithm.h +++ b/src/iguana/algorithms/physics/Depolarization/Algorithm.h @@ -4,16 +4,12 @@ namespace iguana::physics { - /// @brief_algo Calculate depolarization factors + /// @algo_brief{Calculate depolarization factors} + /// @algo_type_creator /// /// @par References /// - https://arxiv.org/pdf/hep-ph/0611265 /// - https://arxiv.org/pdf/1408.5721 - /// - /// @begin_doc_algo{physics::Depolarization | Creator} - /// @end_doc - /// - /// @creator_note class Depolarization : public Algorithm { diff --git a/src/iguana/algorithms/physics/DihadronKinematics/Algorithm.h b/src/iguana/algorithms/physics/DihadronKinematics/Algorithm.h index afe648ef7..b1d0fb2b1 100644 --- a/src/iguana/algorithms/physics/DihadronKinematics/Algorithm.h +++ b/src/iguana/algorithms/physics/DihadronKinematics/Algorithm.h @@ -7,10 +7,8 @@ namespace iguana::physics { - /// @brief_algo Calculate semi-inclusive dihadron kinematic quantities defined in `iguana::physics::DihadronKinematicsVars` - /// - /// @begin_doc_algo{physics::DihadronKinematics | Creator} - /// @end_doc + /// @algo_brief{Calculate semi-inclusive dihadron kinematic quantities defined in `iguana::physics::DihadronKinematicsVars`} + /// @algo_type_creator /// /// @begin_doc_config{physics/DihadronKinematics} /// @config_param{hadron_a_list | list[int] | list of "hadron A" PDGs} @@ -34,8 +32,6 @@ namespace iguana::physics { /// /// @par theta calculation methods /// - `"hadron_a"`: use hadron A's "decay angle" in the dihadron rest frame - /// - /// @creator_note class DihadronKinematics : public Algorithm { diff --git a/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h b/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h index 070c7127a..8cc6acecf 100644 --- a/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h +++ b/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h @@ -7,11 +7,8 @@ namespace iguana::physics { - /// @brief_algo Calculate inclusive kinematics quantities - /// - /// @begin_doc_algo{physics::InclusiveKinematics | Creator} - /// @end_doc - /// + /// @algo_brief{Calculate inclusive kinematics quantities} + /// @algo_type_creator /// @begin_doc_config{physics/InclusiveKinematics} /// @config_param{beam_direction | list[double] | beam direction vector} /// @config_param{target_particle | string | target particle} @@ -19,8 +16,6 @@ namespace iguana::physics { /// @config_param{reconstruction | string | kinematics reconstruction method; only `scattered_lepton` is available at this time} /// @config_param{lepton_finder | string | algorithm to find the scattered lepton; only `highest_energy_FD_trigger` is available at this time} /// @end_doc - /// - /// @creator_note class InclusiveKinematics : public Algorithm { diff --git a/src/iguana/algorithms/physics/SingleHadronKinematics/Algorithm.h b/src/iguana/algorithms/physics/SingleHadronKinematics/Algorithm.h index 4e18812f6..ab189434e 100644 --- a/src/iguana/algorithms/physics/SingleHadronKinematics/Algorithm.h +++ b/src/iguana/algorithms/physics/SingleHadronKinematics/Algorithm.h @@ -6,10 +6,8 @@ namespace iguana::physics { - /// @brief_algo Calculate semi-inclusive hadron kinematic quantities - /// - /// @begin_doc_algo{physics::SingleHadronKinematics | Creator} - /// @end_doc + /// @algo_brief{Calculate semi-inclusive hadron kinematic quantities} + /// @algo_type_creator /// /// @begin_doc_config{physics/SingleHadronKinematics} /// @config_param{hadron_list | list[int] | calculate kinematics for these hadron PDGs} @@ -23,8 +21,6 @@ namespace iguana::physics { /// corresponding row in the output bank will be zeroed, since no calculations are performed for /// those particles /// - particles which are not listed in the configuration parameter `hadron_list` will also be filtered out and zeroed - /// - /// @creator_note class SingleHadronKinematics : public Algorithm { From 5b21ed8cf3c547b7e5cf908aab70dc42cf8aa71b Mon Sep 17 00:00:00 2001 From: Christopher Dilks Date: Thu, 18 Sep 2025 11:45:16 -0400 Subject: [PATCH 15/26] doc: better run function docstrings --- doc/gen/Doxyfile.in | 15 ++++++++------- src/iguana/algorithms/Algorithm.h | 3 ++- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/doc/gen/Doxyfile.in b/doc/gen/Doxyfile.in index 53aabe38a..e660f6701 100644 --- a/doc/gen/Doxyfile.in +++ b/doc/gen/Doxyfile.in @@ -286,16 +286,17 @@ TAB_SIZE = 4 ALIASES = # algorithm properties ALIASES += algo_brief{1}="@brief **%Algorithm:** \1^^@xrefitem algo \"\" \"\" \1^^" -ALIASES += algo_type_filter="@par Type: Filter^^This algorithm will filter input bank(s).^^" -ALIASES += algo_type_transformer="@par Type: Transformer^^This algorithm will change values within input bank(s).^^" -ALIASES += algo_type_creator="@par Type: Creator^^This algorithm creates new bank(s) and its definition is found within the \link src/iguana/bankdefs/iguana.json **Iguana Bank Definitions JSON File** \endlink
  • For guidance on how to read this JSON file, [see documentation on created banks](#mainpageCreatedBanks).
  • See also the return value type of this algorithm's action functions, which may be `struct`s with the same set of variables as the created bank.
" +ALIASES += algo_type_filter="@par Type: Filter^^This algorithm will filter input bank(s).^^@link_to_run_ftn" +ALIASES += algo_type_transformer="@par Type: Transformer^^This algorithm will change values within input bank(s).^^@link_to_run_ftn" +ALIASES += algo_type_creator="@par Type: Creator^^This algorithm creates new bank(s) and its definition is found within the \link src/iguana/bankdefs/iguana.json **Iguana Bank Definitions JSON File** \endlink
  • For guidance on how to read this JSON file, [see documentation on created banks](#mainpageCreatedBanks).
  • See also the return value type of this algorithm's action functions, which may be `struct`s with the same set of variables as the created bank.
^^@link_to_run_ftn" # configuration options ALIASES += begin_doc_config{1}="\par Configuration Options:^^YAML configuration, which includes the default option values:^^@include \1/Config.yaml ^^Table of options and descriptions:^^" ALIASES += config_param{3|}="" ALIASES += end_doc="
NameTypeDescription
`\1``\2`\3
^^" # run functions -ALIASES += run_function="@brief **Run Function**" -ALIASES += run_function_returns_true="@returns `true`, _i.e._, this `Run` function does not provide an event-level filter" +ALIASES += run_function="@brief **Run Function:** Process an event's `hipo::bank` objects^^^^The parameter list explains which banks are input (\"in\"), output (\"out\"), or both (\"in,out\").^^" +ALIASES += link_to_run_ftn="@par Input and Output Banks:^^See @link ::Run `Run` function(s) for the banks @endlink that are processed by this algorithm.^^" +ALIASES += run_function_returns_true="@returns `true`, _i.e._, this `%Run` function does not provide an event-level filter" # action functions ALIASES += action_function{1}="\xrefitem action \"Function Type\" \"List of all Action Functions\" \1 \brief **Action Function:** " ALIASES += when_to_call{1}="@note This function should be called **\1**" @@ -715,7 +716,7 @@ SORT_MEMBER_DOCS = YES # this will also influence the order of the classes in the class list. # The default value is: NO. -SORT_BRIEF_DOCS = NO +SORT_BRIEF_DOCS = YES # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the # (brief and detailed) documentation of class members so that constructors and @@ -734,7 +735,7 @@ SORT_MEMBERS_CTORS_1ST = NO # appear in their defined order. # The default value is: NO. -SORT_GROUP_NAMES = NO +SORT_GROUP_NAMES = YES # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by # fully-qualified names, including namespaces. If set to NO, the class list will diff --git a/src/iguana/algorithms/Algorithm.h b/src/iguana/algorithms/Algorithm.h index 3d472efa2..db1481803 100644 --- a/src/iguana/algorithms/Algorithm.h +++ b/src/iguana/algorithms/Algorithm.h @@ -64,11 +64,12 @@ namespace iguana { /// use this method if you intend to use "action functions" instead of `Algorithm::Run`. void Start(); - /// @brief Run this algorithm for an event. + /// @brief **Run Function:** Process an event's `hipo::banklist` /// @param banks the list of banks to process /// @returns a boolean value, which is typically used to decide whether or not to continue analyzing an event, _i.e._, it can be used /// as an _event-level_ filter; not all algorithms use or need this feature; see the algorithm's more specialized `Run` functions, /// which have `hipo::bank` parameters + /// @see Specialized `%Run` function(s) above/below; they take individual `hipo::bank` objects as parameters, and their documentation explains which banks are used by this algorithm and how. virtual bool Run(hipo::banklist& banks) const = 0; /// @brief Finalize this algorithm after all events are processed. From edece9a3b72eae587dd09af48825c3b1570f8b0e Mon Sep 17 00:00:00 2001 From: Christopher Dilks Date: Thu, 18 Sep 2025 12:02:43 -0400 Subject: [PATCH 16/26] doc: link to run functions in brief --- doc/gen/Doxyfile.in | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/gen/Doxyfile.in b/doc/gen/Doxyfile.in index e660f6701..921f9f73c 100644 --- a/doc/gen/Doxyfile.in +++ b/doc/gen/Doxyfile.in @@ -285,10 +285,10 @@ TAB_SIZE = 4 ALIASES = # algorithm properties -ALIASES += algo_brief{1}="@brief **%Algorithm:** \1^^@xrefitem algo \"\" \"\" \1^^" -ALIASES += algo_type_filter="@par Type: Filter^^This algorithm will filter input bank(s).^^@link_to_run_ftn" -ALIASES += algo_type_transformer="@par Type: Transformer^^This algorithm will change values within input bank(s).^^@link_to_run_ftn" -ALIASES += algo_type_creator="@par Type: Creator^^This algorithm creates new bank(s) and its definition is found within the \link src/iguana/bankdefs/iguana.json **Iguana Bank Definitions JSON File** \endlink
  • For guidance on how to read this JSON file, [see documentation on created banks](#mainpageCreatedBanks).
  • See also the return value type of this algorithm's action functions, which may be `struct`s with the same set of variables as the created bank.
^^@link_to_run_ftn" +ALIASES += algo_brief{1}="@brief **%Algorithm:** \1^^@xrefitem algo \"\" \"\" \1^^@link_to_run_ftn" +ALIASES += algo_type_filter="@par Type: Filter^^This algorithm will filter input bank(s).^^" +ALIASES += algo_type_transformer="@par Type: Transformer^^This algorithm will change values within input bank(s).^^" +ALIASES += algo_type_creator="@par Type: Creator^^This algorithm creates new bank(s) and its definition is found within the \link src/iguana/bankdefs/iguana.json **Iguana Bank Definitions JSON File** \endlink
  • For guidance on how to read this JSON file, [see documentation on created banks](#mainpageCreatedBanks).
  • See also the return value type of this algorithm's action functions, which may be `struct`s with the same set of variables as the created bank.
^^" # configuration options ALIASES += begin_doc_config{1}="\par Configuration Options:^^YAML configuration, which includes the default option values:^^@include \1/Config.yaml ^^Table of options and descriptions:^^" ALIASES += config_param{3|}="" From 2ed912480d6d1636288236864bb0bae70ef55f22 Mon Sep 17 00:00:00 2001 From: Christopher Dilks Date: Tue, 14 Oct 2025 22:42:10 -0400 Subject: [PATCH 17/26] fix: update `RGAFiducialFilter` --- .../clas12/RGAFiducialFilter/Algorithm.cc | 34 ++++++---- .../clas12/RGAFiducialFilter/Algorithm.h | 63 ++++++++++++++++--- .../clas12/RGAFiducialFilter/Validator.cc | 5 +- .../clas12/RGAFiducialFilter/Validator.h | 2 +- 4 files changed, 82 insertions(+), 22 deletions(-) diff --git a/src/iguana/algorithms/clas12/RGAFiducialFilter/Algorithm.cc b/src/iguana/algorithms/clas12/RGAFiducialFilter/Algorithm.cc index 53e0d4eb3..dec98f7e2 100644 --- a/src/iguana/algorithms/clas12/RGAFiducialFilter/Algorithm.cc +++ b/src/iguana/algorithms/clas12/RGAFiducialFilter/Algorithm.cc @@ -136,17 +136,29 @@ void RGAFiducialFilter::Start(hipo::banklist& banks) LoadConfig(); } -void RGAFiducialFilter::Run(hipo::banklist& banks) const { - auto& particle = GetBank(banks, b_particle, "REC::Particle"); - auto& conf = GetBank(banks, b_config, "RUN::config"); - auto* cal = m_have_calor ? &GetBank(banks, b_calor, "REC::Calorimeter") : nullptr; - auto* ft = m_have_ft ? &GetBank(banks, b_ft, "REC::ForwardTagger") : nullptr; - auto* traj = m_have_traj ? &GetBank(banks, b_traj, "REC::Traj") : nullptr; - - particle.getMutableRowList().filter([this, &conf, cal, ft, traj](auto bank, auto row) { - const bool keep = Filter(static_cast(row), bank, conf, cal, ft, traj); +bool RGAFiducialFilter::Run(hipo::banklist& banks) const +{ + return Run( + GetBank(banks, b_particle, "REC::Particle"), + GetBank(banks, b_config, "RUN::config"), + m_have_calor ? &GetBank(banks, b_calor, "REC::Calorimeter") : nullptr, + m_have_traj ? &GetBank(banks, b_traj, "REC::Traj") : nullptr, + m_have_ft ? &GetBank(banks, b_ft, "REC::ForwardTagger") : nullptr + ); +} + +bool RGAFiducialFilter::Run( + hipo::bank& particle, + hipo::bank const& conf, + hipo::bank const* cal, + hipo::bank const* traj, + hipo::bank const* ft) const +{ + particle.getMutableRowList().filter([this, &conf, cal, traj, ft](auto bank, auto row) { + const bool keep = Filter(row, bank, conf, cal, traj, ft); return keep ? 1 : 0; }); + return true; } RGAFiducialFilter::CalLayers @@ -315,7 +327,7 @@ bool RGAFiducialFilter::PassDCFiducial(int pindex, const hipo::bank& particleBan bool RGAFiducialFilter::Filter(int track_index, const hipo::bank& particleBank, const hipo::bank& configBank, const hipo::bank* calBank, - const hipo::bank* ftBank, const hipo::bank* trajBank) const { + const hipo::bank* trajBank, const hipo::bank* ftBank) const { const int pid = particleBank.getInt("pid", track_index); const int strictness = m_cal_strictness; @@ -369,4 +381,4 @@ bool RGAFiducialFilter::Filter(int track_index, const hipo::bank& particleBank, return true; } -} \ No newline at end of file +} diff --git a/src/iguana/algorithms/clas12/RGAFiducialFilter/Algorithm.h b/src/iguana/algorithms/clas12/RGAFiducialFilter/Algorithm.h index 192f1bd95..e81b12087 100644 --- a/src/iguana/algorithms/clas12/RGAFiducialFilter/Algorithm.h +++ b/src/iguana/algorithms/clas12/RGAFiducialFilter/Algorithm.h @@ -4,7 +4,8 @@ namespace iguana::clas12 { - /// @brief_algo Filter the `REC::Particle` bank using subsystem-specific fiducial cuts + /// @algo_brief{Filter the `REC::Particle` bank using subsystem-specific fiducial cuts} + /// @algo_type_filter /// /// RGA fiducial filter: /// @@ -15,10 +16,10 @@ namespace iguana::clas12 { /// - Drift Chamber (DC) fiducial: /// - three region edge thresholds with separate inbending/outbending track logic /// - /// @begin_doc_algo{clas12::RGAFiducialFilter | Filter} - /// @input_banks{REC::Particle, RUN::config, REC::Calorimeter, REC::ForwardTagger, REC::Traj} - /// @output_banks{REC::Particle} - /// @end_doc + /// **NOTE:** this algorithm has multiple `Run(hipo::bank bank1, ...)` functions, which + /// take `hipo::bank` parameters, and some parameters may be optional, since you may + /// be reading data which lack certain banks. If you use these functions, take a look at all them + /// to decide which one best suits your use case. /// /// @begin_doc_config{clas12/RGAFiducialFilter} /// @config_param{calorimeter.strictness | int | calorimeter cut strictness} @@ -58,11 +59,57 @@ namespace iguana::clas12 { }; public: - // algorithm API + void Start(hipo::banklist& banks) override; - void Run (hipo::banklist& banks) const override; + bool Run (hipo::banklist& banks) const override; void Stop () override {} + /// @run_function + /// @param [in,out] particle `REC::Particle` bank, which will be filtered + /// @param [in] conf `RUN::config` bank + /// @param [in] cal pointer to `REC::Calorimeter` bank; it is a _pointer_ since it is _optional_ (use `nullptr` for "unused") + /// @param [in] traj pointer to `REC::Traj` bank; it is a _pointer_ since it is _optional_ (use `nullptr` for "unused") + /// @param [in] ft pointer to `REC::ForwardTagger` bank; it is a _pointer_ since it is _optional_ (use `nullptr` for "unused") + /// @run_function_returns_true + bool Run( + hipo::bank& particle, + hipo::bank const& conf, + hipo::bank const* cal, + hipo::bank const* traj, + hipo::bank const* ft) const; + + /// @run_function + /// @param [in,out] particle `REC::Particle` bank, which will be filtered + /// @param [in] conf `RUN::config` bank + /// @param [in] cal `REC::Calorimeter` bank + /// @param [in] traj `REC::Traj` bank + /// @run_function_returns_true + bool Run( + hipo::bank& particle, + hipo::bank const& conf, + hipo::bank const& cal, + hipo::bank const& traj) const + { + return Run(particle, conf, &cal, &traj, nullptr); + } + + /// @run_function + /// @param [in,out] particle `REC::Particle` bank, which will be filtered + /// @param [in] conf `RUN::config` bank + /// @param [in] cal `REC::Calorimeter` bank + /// @param [in] traj `REC::Traj` bank + /// @param [in] ft `REC::ForwardTagger` bank + /// @run_function_returns_true + bool Run( + hipo::bank& particle, + hipo::bank const& conf, + hipo::bank const& cal, + hipo::bank const& traj, + hipo::bank const& ft) const + { + return Run(particle, conf, &cal, &traj, &ft); + } + /// @returns calorimeter strictness int CalStrictness() const { return m_cal_strictness; } /// @returns FT configuration parameters @@ -97,7 +144,7 @@ namespace iguana::clas12 { bool PassDCFiducial(int track_index, const hipo::bank& particleBank, const hipo::bank& configBank, const hipo::bank* trajBank) const; bool Filter(int track_index, const hipo::bank& particleBank, const hipo::bank& configBank, - const hipo::bank* calBank, const hipo::bank* ftBank, const hipo::bank* trajBank) const; + const hipo::bank* calBank, const hipo::bank* trajBank, const hipo::bank* ftBank) const; // ---- Config loading void LoadConfig(); diff --git a/src/iguana/algorithms/clas12/RGAFiducialFilter/Validator.cc b/src/iguana/algorithms/clas12/RGAFiducialFilter/Validator.cc index e896d2a86..3c41956c7 100644 --- a/src/iguana/algorithms/clas12/RGAFiducialFilter/Validator.cc +++ b/src/iguana/algorithms/clas12/RGAFiducialFilter/Validator.cc @@ -175,7 +175,7 @@ static inline void SetDCPadMargins() { gPad->SetTopMargin(0.08); } -void RGAFiducialFilterValidator::Run(hipo::banklist& banks) const { +bool RGAFiducialFilterValidator::Run(hipo::banklist& banks) const { auto& particle = GetBank(banks, b_particle, "REC::Particle"); auto& config = GetBank(banks, b_config, "RUN::config"); @@ -419,6 +419,7 @@ void RGAFiducialFilterValidator::Run(hipo::banklist& banks) const { const_cast(this)->m_dc_neg_before_n += (long long) inter3(neg_b1, neg_b2, neg_b3); const_cast(this)->m_dc_neg_after_n += (long long) inter3(neg_a1, neg_a2, neg_a3); } + return true; } // plotting @@ -678,4 +679,4 @@ void RGAFiducialFilterValidator::Stop() { gROOT->GetListOfCanvases()->Delete(); } -} \ No newline at end of file +} diff --git a/src/iguana/algorithms/clas12/RGAFiducialFilter/Validator.h b/src/iguana/algorithms/clas12/RGAFiducialFilter/Validator.h index 5e1e61981..36ea9fb6c 100644 --- a/src/iguana/algorithms/clas12/RGAFiducialFilter/Validator.h +++ b/src/iguana/algorithms/clas12/RGAFiducialFilter/Validator.h @@ -21,7 +21,7 @@ class RGAFiducialFilterValidator : public Validator { public: void Start(hipo::banklist& banks) override; - void Run (hipo::banklist& banks) const override; + bool Run (hipo::banklist& banks) const override; void Stop () override; private: From 126b36670d692885379fc2d368ddb4dc0e17af78 Mon Sep 17 00:00:00 2001 From: Christopher Dilks Date: Thu, 16 Oct 2025 10:01:25 -0400 Subject: [PATCH 18/26] fix: implement useful `Run` function return values --- .../clas12/EventBuilderFilter/Algorithm.cc | 4 +++- .../clas12/EventBuilderFilter/Algorithm.h | 2 +- .../clas12/FiducialFilter/Algorithm.cc | 2 +- .../clas12/FiducialFilter/Algorithm.h | 2 +- .../clas12/PhotonGBTFilter/Algorithm.cc | 4 ++-- .../clas12/PhotonGBTFilter/Algorithm.h | 2 +- .../clas12/RGAFiducialFilter/Algorithm.cc | 2 +- .../clas12/RGAFiducialFilter/Algorithm.h | 6 +++--- .../clas12/ZVertexFilter/Algorithm.cc | 2 +- .../algorithms/clas12/ZVertexFilter/Algorithm.h | 2 +- .../example/ExampleAlgorithm/Algorithm.cc | 17 +++++++++-------- .../example/ExampleAlgorithm/Algorithm.h | 2 +- .../physics/DihadronKinematics/Algorithm.h | 2 +- .../physics/InclusiveKinematics/Algorithm.h | 2 +- .../physics/SingleHadronKinematics/Algorithm.h | 2 +- 15 files changed, 28 insertions(+), 25 deletions(-) diff --git a/src/iguana/algorithms/clas12/EventBuilderFilter/Algorithm.cc b/src/iguana/algorithms/clas12/EventBuilderFilter/Algorithm.cc index 3162beb8b..2863c103c 100644 --- a/src/iguana/algorithms/clas12/EventBuilderFilter/Algorithm.cc +++ b/src/iguana/algorithms/clas12/EventBuilderFilter/Algorithm.cc @@ -36,7 +36,9 @@ namespace iguana::clas12 { // dump the modified bank ShowBank(particleBank, Logger::Header("OUTPUT PARTICLES")); - return true; + + // return false if everything is filtered out + return ! particleBank.getRowList().empty(); } diff --git a/src/iguana/algorithms/clas12/EventBuilderFilter/Algorithm.h b/src/iguana/algorithms/clas12/EventBuilderFilter/Algorithm.h index 30fb4f245..aeb2706e7 100644 --- a/src/iguana/algorithms/clas12/EventBuilderFilter/Algorithm.h +++ b/src/iguana/algorithms/clas12/EventBuilderFilter/Algorithm.h @@ -23,7 +23,7 @@ namespace iguana::clas12 { /// @run_function /// @param [in,out] particleBank `REC::Particle`, which will be filtered - /// @run_function_returns_true + /// @returns `false` if all particles are filtered out bool Run(hipo::bank& particleBank) const; /// @action_function{scalar filter} checks if the PDG `pid` is a part of the list of user-specified PDGs diff --git a/src/iguana/algorithms/clas12/FiducialFilter/Algorithm.cc b/src/iguana/algorithms/clas12/FiducialFilter/Algorithm.cc index 259957dc8..0403a383b 100644 --- a/src/iguana/algorithms/clas12/FiducialFilter/Algorithm.cc +++ b/src/iguana/algorithms/clas12/FiducialFilter/Algorithm.cc @@ -74,7 +74,7 @@ namespace iguana::clas12 { ); ShowBank(particleBank, Logger::Header("OUTPUT PARTICLES")); - return true; + return ! particleBank.getRowList().empty(); } ////////////////////////////////////////////////////////////////////////////////// diff --git a/src/iguana/algorithms/clas12/FiducialFilter/Algorithm.h b/src/iguana/algorithms/clas12/FiducialFilter/Algorithm.h index 6cf940033..43afdfaed 100644 --- a/src/iguana/algorithms/clas12/FiducialFilter/Algorithm.h +++ b/src/iguana/algorithms/clas12/FiducialFilter/Algorithm.h @@ -44,7 +44,7 @@ namespace iguana::clas12 { /// @param [in] configBank `RUN::config` /// @param [in] trajBank `REC::Particle::Traj`, created by algorithm `clas12::TrajLinker` /// @param [in] calBank `REC::Particle::Calorimeter`, created by algorithm `clas12::CalorimeterLinker` - /// @run_function_returns_true + /// @returns `false` if all particles are filtered out bool Run( hipo::bank& particleBank, hipo::bank const& configBank, diff --git a/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.cc b/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.cc index a201d4e91..4f425d2f0 100644 --- a/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.cc +++ b/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.cc @@ -67,7 +67,7 @@ namespace iguana::clas12 { // Loop over each photon in the particleBank to classify it // Here we loop over the particleBank RowList // This ensures we are only concerned with filtering photons that passed upstream filters - particleBank.getMutableRowList().filter([this, &caloBank, &calo_map, runnum](auto bank, auto row) { + particleBank.getMutableRowList().filter([this](auto bank, auto row) { auto pid = bank.getInt("pid", row); if (pid != 22) return true; return Filter(bank, caloBank, calo_map, row, runnum); @@ -75,7 +75,7 @@ namespace iguana::clas12 { // dump the modified bank ShowBank(particleBank, Logger::Header("OUTPUT PARTICLES")); - return true; + return ! particleBank.getRowList().empty(); } bool PhotonGBTFilter::PidPurityPhotonFilter(float const E, float const Epcal, float const theta) const diff --git a/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.h b/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.h index 9f807c0b6..801ee02e3 100644 --- a/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.h +++ b/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.h @@ -32,7 +32,7 @@ namespace iguana::clas12 { /// @param [in,out] particleBank `REC::Particle`, which will be filtered /// @param [in] caloBank `REC::Calorimeter` /// @param [in] configBank `RUN::config` - /// @run_function_returns_true + /// @returns `false` if all particles are filtered out bool Run( hipo::bank& particleBank, hipo::bank const& caloBank, diff --git a/src/iguana/algorithms/clas12/RGAFiducialFilter/Algorithm.cc b/src/iguana/algorithms/clas12/RGAFiducialFilter/Algorithm.cc index dec98f7e2..24beebcfc 100644 --- a/src/iguana/algorithms/clas12/RGAFiducialFilter/Algorithm.cc +++ b/src/iguana/algorithms/clas12/RGAFiducialFilter/Algorithm.cc @@ -158,7 +158,7 @@ bool RGAFiducialFilter::Run( const bool keep = Filter(row, bank, conf, cal, traj, ft); return keep ? 1 : 0; }); - return true; + return ! particle.getRowList().empty(); } RGAFiducialFilter::CalLayers diff --git a/src/iguana/algorithms/clas12/RGAFiducialFilter/Algorithm.h b/src/iguana/algorithms/clas12/RGAFiducialFilter/Algorithm.h index e81b12087..1d758c1b2 100644 --- a/src/iguana/algorithms/clas12/RGAFiducialFilter/Algorithm.h +++ b/src/iguana/algorithms/clas12/RGAFiducialFilter/Algorithm.h @@ -70,7 +70,7 @@ namespace iguana::clas12 { /// @param [in] cal pointer to `REC::Calorimeter` bank; it is a _pointer_ since it is _optional_ (use `nullptr` for "unused") /// @param [in] traj pointer to `REC::Traj` bank; it is a _pointer_ since it is _optional_ (use `nullptr` for "unused") /// @param [in] ft pointer to `REC::ForwardTagger` bank; it is a _pointer_ since it is _optional_ (use `nullptr` for "unused") - /// @run_function_returns_true + /// @returns `false` if all particles are filtered out bool Run( hipo::bank& particle, hipo::bank const& conf, @@ -83,7 +83,7 @@ namespace iguana::clas12 { /// @param [in] conf `RUN::config` bank /// @param [in] cal `REC::Calorimeter` bank /// @param [in] traj `REC::Traj` bank - /// @run_function_returns_true + /// @returns `false` if all particles are filtered out bool Run( hipo::bank& particle, hipo::bank const& conf, @@ -99,7 +99,7 @@ namespace iguana::clas12 { /// @param [in] cal `REC::Calorimeter` bank /// @param [in] traj `REC::Traj` bank /// @param [in] ft `REC::ForwardTagger` bank - /// @run_function_returns_true + /// @returns `false` if all particles are filtered out bool Run( hipo::bank& particle, hipo::bank const& conf, diff --git a/src/iguana/algorithms/clas12/ZVertexFilter/Algorithm.cc b/src/iguana/algorithms/clas12/ZVertexFilter/Algorithm.cc index ca6cbe060..ededd9e19 100644 --- a/src/iguana/algorithms/clas12/ZVertexFilter/Algorithm.cc +++ b/src/iguana/algorithms/clas12/ZVertexFilter/Algorithm.cc @@ -45,7 +45,7 @@ namespace iguana::clas12 { // dump the modified bank ShowBank(particleBank, Logger::Header("OUTPUT PARTICLES")); - return true; + return ! particleBank.getRowList().empty(); } concurrent_key_t ZVertexFilter::PrepareEvent(int const runnum) const { diff --git a/src/iguana/algorithms/clas12/ZVertexFilter/Algorithm.h b/src/iguana/algorithms/clas12/ZVertexFilter/Algorithm.h index 6e8dcc9fc..6d8366166 100644 --- a/src/iguana/algorithms/clas12/ZVertexFilter/Algorithm.h +++ b/src/iguana/algorithms/clas12/ZVertexFilter/Algorithm.h @@ -25,7 +25,7 @@ namespace iguana::clas12 { /// @run_function /// @param [in,out] particleBank `REC::Particle`, which will be filtered /// @param [in] configBank `RUN::config` - /// @run_function_returns_true + /// @returns `false` if all particles are filtered out bool Run(hipo::bank& particleBank, hipo::bank const& configBank) const; /// @action_function{reload} prepare the event diff --git a/src/iguana/algorithms/example/ExampleAlgorithm/Algorithm.cc b/src/iguana/algorithms/example/ExampleAlgorithm/Algorithm.cc index 71f0cde6f..b31dae7b7 100644 --- a/src/iguana/algorithms/example/ExampleAlgorithm/Algorithm.cc +++ b/src/iguana/algorithms/example/ExampleAlgorithm/Algorithm.cc @@ -53,12 +53,7 @@ namespace iguana::example { // ############################################################################ // # define `ExampleAlgorithm::Run` functions - // # - note that this method must be _thread safe_, for example, you cannot modify - // # class instance objects - // # - try to avoid expensive operations here; instead, put them in the `Start` method - // # if it is reasonable to do so - // # - the function's `bool` return value can be used as an event-level filter - // # - the `Run` function that acts on `hipo::banklist` should just call the `Run` + // # - this `Run` function that acts on `hipo::banklist` should just call the `Run` // # function that acts on `hipo::bank` objects; let's define it first // ############################################################################ bool ExampleAlgorithm::Run(hipo::banklist& banks) const @@ -71,6 +66,11 @@ namespace iguana::example { // ############################################################################ // # here is the `Run` function which acts on `hipo::bank` objects + // # - note that this method must be _thread safe_, for example, you cannot modify + // # class instance objects; therefore it _must_ be `const` + // # - try to avoid expensive operations here; instead, put them in the `Start` method + // # if it is reasonable to do so + // # - the function's `bool` return value can be used as an event-level filter // ############################################################################ bool ExampleAlgorithm::Run(hipo::bank& particleBank) const { @@ -109,9 +109,10 @@ namespace iguana::example { ShowBank(particleBank, Logger::Header("OUTPUT PARTICLES")); // ############################################################################ - // # return true or false, used as an event-level filter + // # return true or false, used as an event-level filter; in this case, we + // # return false if all particles have been filtered out // ############################################################################ - return true; + return ! particleBank.getRowList().empty(); } diff --git a/src/iguana/algorithms/example/ExampleAlgorithm/Algorithm.h b/src/iguana/algorithms/example/ExampleAlgorithm/Algorithm.h index d3756073e..cd7d0a839 100644 --- a/src/iguana/algorithms/example/ExampleAlgorithm/Algorithm.h +++ b/src/iguana/algorithms/example/ExampleAlgorithm/Algorithm.h @@ -78,7 +78,7 @@ namespace iguana::example { // ############################################################################ /// @run_function /// @param [in,out] particleBank `REC::Particle` bank - /// @run_function_returns_true + /// @returns `false` if all particles are filtered out bool Run(hipo::bank& particleBank) const; // ############################################################################ diff --git a/src/iguana/algorithms/physics/DihadronKinematics/Algorithm.h b/src/iguana/algorithms/physics/DihadronKinematics/Algorithm.h index b1d0fb2b1..9d0362aee 100644 --- a/src/iguana/algorithms/physics/DihadronKinematics/Algorithm.h +++ b/src/iguana/algorithms/physics/DihadronKinematics/Algorithm.h @@ -47,7 +47,7 @@ namespace iguana::physics { /// @param [in] particle_bank `REC::Particle` /// @param [in] inc_kin_bank `%physics::InclusiveKinematics`, produced by the `physics::InclusiveKinematics` algorithm /// @param [out] result_bank `%physics::DihadronKinematics`, which will be created - /// @run_function_returns_true + /// @returns `false` if the input banks do not have enough information, _e.g._, if the inclusive kinematics bank is empty bool Run( hipo::bank const& particle_bank, hipo::bank const& inc_kin_bank, diff --git a/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h b/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h index 8cc6acecf..39720c220 100644 --- a/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h +++ b/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h @@ -31,7 +31,7 @@ namespace iguana::physics { /// @param [in] particle_bank `REC::Particle` /// @param [in] config_bank `RUN::config` /// @param [out] result_bank `%physics::InclusiveKinematics`, which will be created - /// @returns `true` if the kinematics were calculated, _e.g._, if the calculations are performed using + /// @returns `true` if the kinematics were calculated; _e.g._, if the calculations are performed using /// the scattered lepton, and no scattered lepton was found, `false` will be returned bool Run( hipo::bank const& particle_bank, diff --git a/src/iguana/algorithms/physics/SingleHadronKinematics/Algorithm.h b/src/iguana/algorithms/physics/SingleHadronKinematics/Algorithm.h index ab189434e..650859b18 100644 --- a/src/iguana/algorithms/physics/SingleHadronKinematics/Algorithm.h +++ b/src/iguana/algorithms/physics/SingleHadronKinematics/Algorithm.h @@ -36,7 +36,7 @@ namespace iguana::physics { /// @param [in] particle_bank `REC::Particle` /// @param [in] inc_kin_bank `%physics::InclusiveKinematics`, produced by the `physics::InclusiveKinematics` algorithm /// @param [out] result_bank `%physics::SingleHadronKinematics`, which will be created - /// @run_function_returns_true + /// @returns `false` if the input banks do not have enough information, _e.g._, if the inclusive kinematics bank is empty bool Run( hipo::bank const& particle_bank, hipo::bank const& inc_kin_bank, From 4fd0dd36df7fe6b63bf93db54ed812c9214b40e0 Mon Sep 17 00:00:00 2001 From: Christopher Dilks Date: Thu, 16 Oct 2025 18:11:14 -0400 Subject: [PATCH 19/26] doc: reference for `RGAFiducialFilter` --- src/iguana/algorithms/clas12/RGAFiducialFilter/Algorithm.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/iguana/algorithms/clas12/RGAFiducialFilter/Algorithm.h b/src/iguana/algorithms/clas12/RGAFiducialFilter/Algorithm.h index 1d758c1b2..e124bc85b 100644 --- a/src/iguana/algorithms/clas12/RGAFiducialFilter/Algorithm.h +++ b/src/iguana/algorithms/clas12/RGAFiducialFilter/Algorithm.h @@ -16,6 +16,10 @@ namespace iguana::clas12 { /// - Drift Chamber (DC) fiducial: /// - three region edge thresholds with separate inbending/outbending track logic /// + /// **References:** + /// + /// - https://clas12-docdb.jlab.org/DocDB/0012/001240/001/rga_fiducial_cuts.pdf + /// /// **NOTE:** this algorithm has multiple `Run(hipo::bank bank1, ...)` functions, which /// take `hipo::bank` parameters, and some parameters may be optional, since you may /// be reading data which lack certain banks. If you use these functions, take a look at all them From be150f5ca576617295540fb17136c563ba07ec40 Mon Sep 17 00:00:00 2001 From: Christopher Dilks Date: Thu, 16 Oct 2025 18:28:32 -0400 Subject: [PATCH 20/26] fix: captures are actually needed --- src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.cc b/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.cc index 4f425d2f0..4680df790 100644 --- a/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.cc +++ b/src/iguana/algorithms/clas12/PhotonGBTFilter/Algorithm.cc @@ -67,7 +67,7 @@ namespace iguana::clas12 { // Loop over each photon in the particleBank to classify it // Here we loop over the particleBank RowList // This ensures we are only concerned with filtering photons that passed upstream filters - particleBank.getMutableRowList().filter([this](auto bank, auto row) { + particleBank.getMutableRowList().filter([this, &caloBank, &calo_map, runnum](auto bank, auto row) { auto pid = bank.getInt("pid", row); if (pid != 22) return true; return Filter(bank, caloBank, calo_map, row, runnum); From b5f3a1082ccca627a4703ef3cb0daf1d7f528471 Mon Sep 17 00:00:00 2001 From: Christopher Dilks Date: Thu, 16 Oct 2025 19:43:49 -0400 Subject: [PATCH 21/26] doc!: make created-bank tables, so users don't have to understand JSON --- doc/gen/Doxyfile.in | 3 +- doc/gen/mainpage.md | 51 ++++++------------------------- src/iguana/bankdefs/bankgen.py | 54 ++++++++++++++++++++++++++++++--- src/iguana/bankdefs/iguana.json | 14 ++++----- src/iguana/bankdefs/meson.build | 4 +-- 5 files changed, 70 insertions(+), 56 deletions(-) diff --git a/doc/gen/Doxyfile.in b/doc/gen/Doxyfile.in index 921f9f73c..e7e3999ae 100644 --- a/doc/gen/Doxyfile.in +++ b/doc/gen/Doxyfile.in @@ -288,7 +288,7 @@ ALIASES = ALIASES += algo_brief{1}="@brief **%Algorithm:** \1^^@xrefitem algo \"\" \"\" \1^^@link_to_run_ftn" ALIASES += algo_type_filter="@par Type: Filter^^This algorithm will filter input bank(s).^^" ALIASES += algo_type_transformer="@par Type: Transformer^^This algorithm will change values within input bank(s).^^" -ALIASES += algo_type_creator="@par Type: Creator^^This algorithm creates new bank(s) and its definition is found within the \link src/iguana/bankdefs/iguana.json **Iguana Bank Definitions JSON File** \endlink
  • For guidance on how to read this JSON file, [see documentation on created banks](#mainpageCreatedBanks).
  • See also the return value type of this algorithm's action functions, which may be `struct`s with the same set of variables as the created bank.
^^" +ALIASES += algo_type_creator="@par Type: Creator^^^^- This algorithm creates new bank(s); \ref created_banks \"click here for a description of all created banks\".^^- See also the return value type of this algorithm's action functions, which may be `struct`s with the same set of variables as the created bank.^^^^" # configuration options ALIASES += begin_doc_config{1}="\par Configuration Options:^^YAML configuration, which includes the default option values:^^@include \1/Config.yaml ^^Table of options and descriptions:^^
NameTypeDescription
`\1``\2`\3
" ALIASES += config_param{3|}="" @@ -973,6 +973,7 @@ WARN_LOGFILE = INPUT = @top_srcdir@/src/ \ @top_builddir@/src/iguana/algorithms \ @top_builddir@/src/iguana/bankdefs \ + @top_builddir@/src/iguana/bankdefs/BankDefs.md \ @top_srcdir@/bind/ \ @top_srcdir@/doc/gen/ \ @top_srcdir@/doc/gen/mainpage.md \ diff --git a/doc/gen/mainpage.md b/doc/gen/mainpage.md index 16bab584d..e49ce93a5 100644 --- a/doc/gen/mainpage.md +++ b/doc/gen/mainpage.md @@ -7,6 +7,7 @@ This documentation shows how to use the Iguana algorithms. For more documentatio | --- | --- | | @spacer [List of Algorithms](#algo) @spacer | @spacer [Examples of Code](#mainpageExample) @spacer | | @spacer [List of Action Functions](#action) @spacer | @spacer [Configuring Algorithms](#mainpageConfiguring) @spacer | +| @spacer [Banks Created by Iguana](#created_banks) @spacer | |

@@ -61,44 +62,6 @@ The available algorithms are: - [Algorithms organized by Namespace](#algo_namespaces) - [Full List of Algorithms](#algo) -@anchor mainpageCreatedBanks -### New Banks from Iguana Creator Algorithms - -The definitions of the new banks that are created by **Creator** algorithms are found in: -- @link src/iguana/bankdefs/iguana.json **Iguana Bank Definitions:** `iguana.json` @endlink - -This JSON file follows a similar format as the bank definitions in `coatjava`, where we have: - -| Key | Description | -| --- | --- | -| name | the name of the new bank | -| algorithm | the algorithm that creates this bank | -| group | unique ID numbers for this bank | -| item | ^ | -| entries | the list of variables in this bank | - -Often the bank name matches the algorithm name, but not always; see the JSON keys \"name\" and \"algorithm\" to be sure. - -For each variable in "entries", we have: - -| Key | Description | -| --- | --- | -| name | the variable name | -| type | the variable type (see below) | -| info | the description of this variable | - -The variable types and their corresponding accessor methods from `hipo::bank` are: - -| Type Specification | `hipo::bank` accessor | -| --- | --- | -| B | `getByte` | -| S | `getShort` | -| I | `getInt` | -| L | `getLong` | -| F | `getFloat` | -| D | `getDouble` | - -


@@ -156,9 +119,15 @@ typically change the particle momentum components.
NameTypeDescription
`\1``\2`\3
**Creator** Creator-type algorithms will simply create a new `hipo::bank` object, appending -it to the end of the input `hipo::banklist`. An initial version is created upon calling -iguana::Algorithm::Start(hipo::banklist&), so that you may begin to reference it; it is helpful to -use `hipo::getBanklistIndex` (see [the examples for details](#mainpageExample)). +it to the end of the input `hipo::banklist`. +
    +
  • [Click here for descriptions of all created banks](#created_banks)
  • +
  • An initial version is created upon calling iguana::Algorithm::Start(hipo::banklist&), so that you may begin to reference it
  • +
      +
    • It is helpful to use `hipo::getBanklistIndex`, to get the created bank index within the `hipo::banklist`
    • +
    • See [the examples for details](#mainpageExample)
    • +
    +
diff --git a/src/iguana/bankdefs/bankgen.py b/src/iguana/bankdefs/bankgen.py index bda528f2d..1cdba6b27 100755 --- a/src/iguana/bankdefs/bankgen.py +++ b/src/iguana/bankdefs/bankgen.py @@ -30,6 +30,15 @@ def trailing_comma(arr, idx): 'D': 'double', 'L': 'long', } +# and to a more standard name, for documentation +type_name_dict = { + 'B': 'byte', + 'S': 'short', + 'I': 'int', + 'F': 'float', + 'D': 'double', + 'L': 'long', +} # all iguana banks should have this group ID iguana_group_id = 30000 @@ -40,9 +49,10 @@ def trailing_comma(arr, idx): try: bank_defs = json.load(input_file) - # start the output C++ files + # start the output files out_h = open(f'{output_base_name}.h', 'w') out_cc = open(f'{output_base_name}.cc', 'w') + out_md = open(f'{output_base_name}.md', 'w') # start the header file with some common structs out_h.write(textwrap.dedent('''\ @@ -87,6 +97,24 @@ def trailing_comma(arr, idx): std::vector const BANK_DEFS = {{ ''')) + # start the documentation `.md` file + out_md.write(textwrap.dedent('''\ + @defgroup created_banks Banks Created by Iguana Algorithms + + The following tables describe banks created by creator-type algorithms. Often the bank name matches the algorithm name, but not always. + + The variable types and their corresponding accessor methods from `hipo::bank` are: + + | Type Specification | `hipo::bank` accessor | + | --- | --- | + | `byte` | `getByte` | + | `short` | `getShort` | + | `int` | `getInt` | + | `long` | `getLong` | + | `float` | `getFloat` | + | `double` | `getDouble` | + ''')) + # loop over bank definitions in the JSON file i_bank_def = 0 unique_item_ids = [] @@ -121,12 +149,11 @@ def trailing_comma(arr, idx): out_cc.write(f' }}{trail_bank_def}\n') # make a struct for this algorithm's action function output - algo_name = bank_def["algorithm"] - namespace_name = '::'.join(['iguana', *algo_name.split('::')[0:-1]]) - struct_name = algo_name.split('::')[-1] + 'Vars' + namespace_name = '::'.join(['iguana', *bank_def['algorithm'].split('::')[0:-1]]) + struct_name = bank_def['algorithm'].split('::')[-1] + 'Vars' out_h.write(textwrap.dedent(f'''\ namespace {namespace_name} {{ - /// Set of variables created by creator algorithm `iguana::{algo_name}` + /// Set of variables created by creator algorithm `iguana::{bank_def['algorithm']}` struct {struct_name} {{ ''')) for entry in bank_def['entries']: @@ -141,10 +168,27 @@ def trailing_comma(arr, idx): out_h.write(' };\n') out_h.write('}\n\n') + # add a table to the documentation + out_md.write(textwrap.dedent(f'''\ + +

+ ## `{bank_def['name']}` + + **Description:** {bank_def['info']} + + **Creator Algorithm:** `iguana::{bank_def['algorithm']}` + + | Variable | Type | Description | + | --- | --- | --- | + ''')) + for entry in bank_def['entries']: + out_md.write(f'| `{entry["name"]}` | `{type_name_dict[entry["type"]]}` | {entry["info"]} |\n') + out_cc.write(' };\n') out_cc.write('}\n') out_cc.close() out_h.close() + out_md.close() except json.decoder.JSONDecodeError: print(f'ERROR: failed to parse {input_file_name}; check its JSON syntax', file=sys.stderr) diff --git a/src/iguana/bankdefs/iguana.json b/src/iguana/bankdefs/iguana.json index e0ae6e116..08842f75d 100644 --- a/src/iguana/bankdefs/iguana.json +++ b/src/iguana/bankdefs/iguana.json @@ -4,7 +4,7 @@ "algorithm": "clas12::SectorFinder", "group": 30000, "item": 1, - "info": "", + "info": "sector information for each particle", "entries": [ { "name": "pindex", "type": "S", "info": "row number in the particle bank" }, { "name": "sector", "type": "I", "info": "sector for this particle" } @@ -15,7 +15,7 @@ "algorithm": "physics::InclusiveKinematics", "group": 30000, "item": 2, - "info": "", + "info": "inclusive kinematics", "entries": [ { "name": "pindex", "type": "S", "info": "`REC::Particle` row (`pindex`) of the scattered electron" }, { "name": "Q2", "type": "D", "info": "@latex{Q^2} (GeV@latex{^2})" }, @@ -36,7 +36,7 @@ "algorithm": "physics::SingleHadronKinematics", "group": 30000, "item": 3, - "info": "", + "info": "SIDIS single-hadron kinematics", "entries": [ { "name": "pindex", "type": "S", "info": "`REC::Particle` row (`pindex`) of the hadron" }, { "name": "pdg", "type": "I", "info": "PDG code of the hadron" }, @@ -54,7 +54,7 @@ "algorithm": "physics::DihadronKinematics", "group": 30000, "item": 4, - "info": "", + "info": "SIDIS dihadron kinematics", "entries": [ { "name": "pindex_a", "type": "S", "info": "`REC::Particle` row (`pindex`) of hadron A" }, { "name": "pindex_b", "type": "S", "info": "`REC::Particle` row (`pindex`) of hadron B" }, @@ -76,7 +76,7 @@ "algorithm": "physics::Depolarization", "group": 30000, "item": 5, - "info": "", + "info": "SIDIS depolarization factors", "entries": [ { "name": "epsilon", "type": "D", "info": "@latex{\\varepsilon(Q^2, x, y)}, the ratio of transverse and longitudinal photon flux" }, { "name": "A", "type": "D", "info": "depolarization factor @latex{A(\\varepsilon, y)}" }, @@ -91,7 +91,7 @@ "algorithm": "clas12::CalorimeterLinker", "group": 30000, "item": 6, - "info": "", + "info": "calorimeter information linked to particles", "entries": [ { "name": "pindex", "type": "S", "info": "row number in the particle bank" }, { "name": "pcal_found", "type": "B", "info": "1 if PCAL info found for this particle, 0 otherwise" }, @@ -119,7 +119,7 @@ "algorithm": "clas12::TrajLinker", "group": 30000, "item": 7, - "info": "", + "info": "trajectory information linked to particles", "entries": [ { "name": "pindex", "type": "S", "info": "row number in the particle bank" }, { "name": "sector", "type": "I", "info": "sector" }, diff --git a/src/iguana/bankdefs/meson.build b/src/iguana/bankdefs/meson.build index 897f6f3f7..720a82bdd 100644 --- a/src/iguana/bankdefs/meson.build +++ b/src/iguana/bankdefs/meson.build @@ -10,10 +10,10 @@ bankdef_tgt = custom_target( bankdef_json, prog_bankgen_sources, ], - output: [ 'BankDefs.h', 'BankDefs.cc' ], + output: [ 'BankDefs.h', 'BankDefs.cc', 'BankDefs.md' ], command: [ prog_bankgen, '@INPUT0@', '@OUTDIR@/BankDefs' ], install: true, - install_dir: [ get_option('includedir') / meson.project_name() / 'bankdefs', false ], + install_dir: [ get_option('includedir') / meson.project_name() / 'bankdefs', false, false ], ) # install the JSON data model file; iguana won't need it, but it can be useful for user reference From 65314a83fa41f0ba7bc3be89fe141c1ef40d55eb Mon Sep 17 00:00:00 2001 From: Christopher Dilks Date: Fri, 17 Oct 2025 13:53:32 -0400 Subject: [PATCH 22/26] doc: usage flowchart --- .gitignore | 1 + doc/gen/Doxyfile.in | 3 +- doc/gen/mainpage.md | 55 +++++++++++++-------- doc/gen/mermaid/build.sh | 9 ++++ doc/gen/mermaid/flowchart_usage.mmd | 72 ++++++++++++++++++++++++++++ doc/gen/mermaid/flowchart_usage.png | Bin 0 -> 161225 bytes 6 files changed, 120 insertions(+), 20 deletions(-) create mode 100755 doc/gen/mermaid/build.sh create mode 100644 doc/gen/mermaid/flowchart_usage.mmd create mode 100644 doc/gen/mermaid/flowchart_usage.png diff --git a/.gitignore b/.gitignore index a75b1a501..aa188eab6 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ chameleon-tree # overrides !doc/gen/logo.png !doc/gen/logo_small.png +!doc/gen/mermaid/*.png # misc. artifacts .DS_Store diff --git a/doc/gen/Doxyfile.in b/doc/gen/Doxyfile.in index e7e3999ae..fa424b614 100644 --- a/doc/gen/Doxyfile.in +++ b/doc/gen/Doxyfile.in @@ -1090,7 +1090,8 @@ EXAMPLE_RECURSIVE = NO # that contain images that are to be included in the documentation (see the # \image command). -IMAGE_PATH = @top_srcdir@/doc/gen/logo.png +IMAGE_PATH = @top_srcdir@/doc/gen/logo.png \ + @top_srcdir@/doc/gen/mermaid/flowchart_usage.png # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program diff --git a/doc/gen/mainpage.md b/doc/gen/mainpage.md index e49ce93a5..e1e6a5271 100644 --- a/doc/gen/mainpage.md +++ b/doc/gen/mainpage.md @@ -23,26 +23,43 @@ To see Iguana algorithms used in the context of analysis code, with **various la | ^ | Includes guidance on how to build Iguana with your C++ code | | @spacer [Python examples](#examples_python) @spacer | For users of Python tools, such as `PyROOT` | | @spacer [Fortran examples](#examples_fortran) @spacer | See also the [Fortran usage guide](#fortran_usage_guide) | +| @spacer Java @spacer | We do not yet support Java, but we plan to soon | In summary, the general way to use an Iguana algorithm is as follows; see examples and documentation below for more details. -1. Decide [which algorithms](#algo) you want to use +1. Decide how you will use Iguana with your analysis code (see also the flowchart, just below) + - Use [Iguana Common Functions](#mainpageCommon) if your analysis uses: + - The [HIPO C++ API](https://github.com/gavalian/hipo) + - [`clas12root`](https://github.com/JeffersonLab/clas12root) + - Our [Our Python bindings](#examples_python) + - Use [Iguana Action Functions](#mainpageAction) otherwise + - Unfortunately, some algorithms are not fully supported by action functions, but we can try to add support upon request +2. Decide [which algorithms](#algo) you want to use - Tip: you may use `iguana::AlgorithmSequence` to help run a _sequence_ of algorithms -2. Check each algorithm configuration, and [adjust it if you prefer](#mainpageConfiguring) -3. Start each algorithm, which "locks in" its configuration: - - call iguana::Algorithm::Start(hipo::banklist&) if you use [**the HIPO API**](https://github.com/gavalian/hipo) and [Common Functions](#mainpageCommon) - - call iguana::Algorithm::Start() otherwise, _i.e._, if you use [Action Functions](#mainpageAction) -4. In the event loop, run the algorithm: - - call iguana::Algorithm::Run(hipo::banklist&) const if you use Common Functions +3. Check each algorithm configuration, and [adjust it if you prefer](#mainpageConfiguring) +4. Start each algorithm, which "locks in" its configuration: + - if using Common Functions: + - call `Start()` if you will be using individual `hipo::bank` objects (_e.g._, if you are using `clas12root`) + - call `Start(hipo::banklist&)` if you use `hipo::banklist` + - if using Action Functions: + - call `Start()` +5. In the event loop, Run the algorithm for each event: + - if using Common Functions, either: + - call specialized `Run` functions, which act on individual `hipo::bank` objects, or + - call `Run(hipo::banklist&)` if you use `hipo::banklist` - call the Action Function(s) otherwise -5. Proceed with your analysis - - if you called iguana::Algorithm::Run(hipo::banklist&) const, the banks will be filtered, transformed, and/or created - - if you called action functions, you will need to handle their output yourself +6. Proceed with your analysis + - if you called a `Run` function, banks will be filtered, transformed, and/or created + - if you called Action Functions, you will need to handle their output yourself - in either case, see [guidance on how to run algorithms](#mainpageRunning) for more details -6. After your event loop, stop each algorithm by calling iguana::Algorithm::Stop() +7. After your event loop, stop each algorithm by calling `Stop()` Please let the maintainers know if your use case is not covered in any examples or if you need any help. +Here is a flowchart, illustrating the above: + + +


@@ -85,13 +102,13 @@ All algorithms have the following **Common Functions**, which may be used in ana | Common Functions || | --- | --- | -| iguana::Algorithm::Start(hipo::banklist&) | To be called before event processing | -| iguana::Algorithm::Run(hipo::banklist&) const | To be called for every event | -| iguana::Algorithm::Stop() | To be called after event processing | +| `Start(hipo::banklist&)` | To be called before event processing | +| `Run(hipo::banklist&)` | To be called for every event | +| `Stop()` | To be called after event processing | The algorithms are implemented in C++ classes which inherit from the base class `iguana::Algorithm`; these three class methods are overridden in each algorithm. -The iguana::Algorithm::Run(hipo::banklist&) const function should be called on every event; the general consequence, that is, how the user should handle the algorithm's results, depends on the +The `Run(hipo::banklist&)` function should be called on every event; the general consequence, that is, how the user should handle the algorithm's results, depends on the algorithm type: @@ -122,7 +139,7 @@ Creator-type algorithms will simply create a new `hipo::bank` object, appending it to the end of the input `hipo::banklist`.
  • [Click here for descriptions of all created banks](#created_banks)
  • -
  • An initial version is created upon calling iguana::Algorithm::Start(hipo::banklist&), so that you may begin to reference it
  • +
  • An initial version is created upon calling `Start(hipo::banklist&)`, so that you may begin to reference it
    • It is helpful to use `hipo::getBanklistIndex`, to get the created bank index within the `hipo::banklist`
    • See [the examples for details](#mainpageExample)
    • @@ -131,7 +148,7 @@ it to the end of the input `hipo::banklist`.
-Internally, iguana::Algorithm::Run(hipo::banklist&) const calls Action Functions, which are described in the next section. +Internally, `Run(hipo::banklist&)` calls Action Functions, which are described in the next section.
@anchor mainpageAction @@ -145,8 +162,8 @@ documentation for details, or browse the full list: - [List of all Action Functions](#action) @note To start an algorithm in order to use action functions, you may `Start` it without a bank list, that is, call -iguana::Algorithm::Start() instead of iguana::Algorithm::Start(hipo::banklist&). Stopping the algorithm is the same -regardless of whether you use action functions or not: call iguana::Algorithm::Stop(). +`Start()` instead of `Start(hipo::banklist&)`. Stopping the algorithm is the same +regardless of whether you use action functions or not: call `Stop()`. Action function parameters are supposed to be _simple_: numbers or lists of numbers, preferably obtainable _directly_ from HIPO bank rows. The return type of an action function depends on the algorithm type: diff --git a/doc/gen/mermaid/build.sh b/doc/gen/mermaid/build.sh new file mode 100755 index 000000000..4ef15965a --- /dev/null +++ b/doc/gen/mermaid/build.sh @@ -0,0 +1,9 @@ +#!/usr/bin/bash +# requires `mermaid-cli` +# NOTE: this is not automated by `meson`, to avoid introducing `mermaid-cli` dependency; +# this is okay, since we do not expect this figure to change often +exec mmdc \ + -b transparent \ + -w 1600 \ + -i flowchart_usage.mmd \ + -o flowchart_usage.png diff --git a/doc/gen/mermaid/flowchart_usage.mmd b/doc/gen/mermaid/flowchart_usage.mmd new file mode 100644 index 000000000..2fe05b802 --- /dev/null +++ b/doc/gen/mermaid/flowchart_usage.mmd @@ -0,0 +1,72 @@ +flowchart TB + + %%{init: { + 'themeVariables': { + 'fontSize': '20px', + 'fontFamily': 'arial', + 'edgeLabelBackground': '#ff8' + } + }}%% + classDef dec fill:#8f8,color:black + classDef cftn fill:#8ff,color:black + classDef sftn fill:#f8f,color:black + classDef aftn fill:#f88,color:black + classDef lang fill:#ff8,color:black + + %% language nodes + subgraph "Start Here" + language{{"What language do you want to use?"}}:::dec + cpp([C++]):::lang + python([Python]):::lang + fortran([Fortran]):::lang + java([Java]):::lang + end + + %% decision nodes + clas12root{{"Do you use clas12root, a version newer than 1.8.6?"}}:::dec + hipo_cpp{{"Do you use the HIPO C++ API directly, e.g., hipo::bank objects?"}}:::dec + hipo_cppyy{{"Do you use cppyy bindings to the HIPO C++ API? (see our Python examples)"}}:::dec + hipo_banklist{{"Do you use hipo::banklist?"}}:::dec + + %% function nodes + %% common function nodes are prefixed with c_ + %% action function nodes are prefixed with a_ + subgraph "before events" + c_start_no_arg["Start()"]:::cftn + c_start_banklist["Start(banklist)"]:::cftn + a_start["Start()"]:::aftn + end + subgraph "for each event" + c_run_banks["Run(bank1, bank2, ...)"]:::cftn + c_run_banklist["Run(banklist)"]:::cftn + a_action["call action function(s)"]:::aftn + end + subgraph "     after events" + stop["Stop()"]:::sftn + end + todo["Not yet supported"]:::lang + fortran_note["You will be using C wrapper functions (see documentation)"]:::lang + + %% edges + language ==> cpp ===> clas12root + language ==> python ===> hipo_cppyy + language ==> fortran ===> fortran_note ==> a_start + language ==> java ===> todo + + clas12root == | YES | ===> c_start_no_arg + clas12root == | NO | ===> hipo_cpp + + hipo_cpp == | YES | ===> hipo_banklist + hipo_cpp == | NO | ===> a_start + + hipo_cppyy == | YES | ===> hipo_banklist + hipo_cppyy == | NO | ===> a_start + + hipo_banklist == | NO | ===> c_start_no_arg + hipo_banklist == | YES | ===> c_start_banklist + + c_start_no_arg ==> c_run_banks ==> stop + c_start_banklist ==> c_run_banklist ==> stop + a_start ==> a_action ==> stop + + diff --git a/doc/gen/mermaid/flowchart_usage.png b/doc/gen/mermaid/flowchart_usage.png new file mode 100644 index 0000000000000000000000000000000000000000..fb72e0d1649e0bdc973179479affec82361e98f2 GIT binary patch literal 161225 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

c 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 Date: Fri, 17 Oct 2025 18:49:09 -0400 Subject: [PATCH 23/26] doc: various improvements --- doc/gen/Doxyfile.in | 4 +- doc/gen/mainpage.md | 96 ++++++++++++++++------------ doc/gen/mermaid/build.sh | 8 +-- doc/gen/mermaid/flowchart_usage.mmd | 24 ++++--- doc/gen/mermaid/flowchart_usage.png | Bin 161225 -> 159708 bytes 5 files changed, 76 insertions(+), 56 deletions(-) diff --git a/doc/gen/Doxyfile.in b/doc/gen/Doxyfile.in index fa424b614..f8bbf4469 100644 --- a/doc/gen/Doxyfile.in +++ b/doc/gen/Doxyfile.in @@ -285,7 +285,7 @@ TAB_SIZE = 4 ALIASES = # algorithm properties -ALIASES += algo_brief{1}="@brief **%Algorithm:** \1^^@xrefitem algo \"\" \"\" \1^^@link_to_run_ftn" +ALIASES += algo_brief{1}="@brief **%Algorithm:** \1^^@xrefitem algo \"\" \"List of Algorithms\" \1^^@link_to_run_ftn" ALIASES += algo_type_filter="@par Type: Filter^^This algorithm will filter input bank(s).^^" ALIASES += algo_type_transformer="@par Type: Transformer^^This algorithm will change values within input bank(s).^^" ALIASES += algo_type_creator="@par Type: Creator^^^^- This algorithm creates new bank(s); \ref created_banks \"click here for a description of all created banks\".^^- See also the return value type of this algorithm's action functions, which may be `struct`s with the same set of variables as the created bank.^^^^" @@ -2550,7 +2550,7 @@ COLLABORATION_GRAPH = NO # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. -GROUP_GRAPHS = YES +GROUP_GRAPHS = NO # If the UML_LOOK tag is set to YES, doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling diff --git a/doc/gen/mainpage.md b/doc/gen/mainpage.md index e1e6a5271..e05b7b363 100644 --- a/doc/gen/mainpage.md +++ b/doc/gen/mainpage.md @@ -23,42 +23,48 @@ To see Iguana algorithms used in the context of analysis code, with **various la | ^ | Includes guidance on how to build Iguana with your C++ code | | @spacer [Python examples](#examples_python) @spacer | For users of Python tools, such as `PyROOT` | | @spacer [Fortran examples](#examples_fortran) @spacer | See also the [Fortran usage guide](#fortran_usage_guide) | -| @spacer Java @spacer | We do not yet support Java, but we plan to soon | +| @spacer **Java** @spacer | We do not yet support Java, but we plan to soon | -In summary, the general way to use an Iguana algorithm is as follows; see examples and documentation below for more details. +

-1. Decide how you will use Iguana with your analysis code (see also the flowchart, just below) +## Usage Summary + +In summary, the general way to use an Iguana algorithm is the following: + +1. Decide how you will use Iguana with your analysis code (see also [the flowchart](#mainpageFlowchart), just below) - Use [Iguana Common Functions](#mainpageCommon) if your analysis uses: - - The [HIPO C++ API](https://github.com/gavalian/hipo) - - [`clas12root`](https://github.com/JeffersonLab/clas12root) + - The [**HIPO C++ API**](https://github.com/gavalian/hipo) + - [**`clas12root`**](https://github.com/JeffersonLab/clas12root) - Our [Our Python bindings](#examples_python) - Use [Iguana Action Functions](#mainpageAction) otherwise - - Unfortunately, some algorithms are not fully supported by action functions, but we can try to add support upon request 2. Decide [which algorithms](#algo) you want to use - - Tip: you may use `iguana::AlgorithmSequence` to help run a _sequence_ of algorithms 3. Check each algorithm configuration, and [adjust it if you prefer](#mainpageConfiguring) 4. Start each algorithm, which "locks in" its configuration: - if using Common Functions: - - call `Start()` if you will be using individual `hipo::bank` objects (_e.g._, if you are using `clas12root`) - - call `Start(hipo::banklist&)` if you use `hipo::banklist` + - call @link iguana::Algorithm::Start() `Start()` @endlink if you will be using individual `hipo::bank` objects (_e.g._, if you are using `clas12root`) + - call @link iguana::Algorithm::Start(hipo::banklist&) `Start(hipo::banklist&)` @endlink if you use `hipo::banklist` + - **Tip:** `hipo::banklist` users may use @link iguana::AlgorithmSequence `AlgorithmSequence` @endlink to help run a _sequence_ of algorithms - if using Action Functions: - - call `Start()` + - call @link iguana::Algorithm::Start() `Start()` @endlink 5. In the event loop, Run the algorithm for each event: - if using Common Functions, either: - - call specialized `Run` functions, which act on individual `hipo::bank` objects, or - - call `Run(hipo::banklist&)` if you use `hipo::banklist` - - call the Action Function(s) otherwise + - call specialized `Run` functions, which act on individual `hipo::bank` objects and are unique for each algorithm; users of `clas12root` versions _newer_ than `1.8.6` should use this + - call @link iguana::Algorithm::Run(hipo::banklist&) const `Run(hipo::banklist&)` @endlink if you use `hipo::banklist` + - call the [Action Function(s)](#action) otherwise 6. Proceed with your analysis - if you called a `Run` function, banks will be filtered, transformed, and/or created - if you called Action Functions, you will need to handle their output yourself - in either case, see [guidance on how to run algorithms](#mainpageRunning) for more details -7. After your event loop, stop each algorithm by calling `Stop()` +7. After your event loop, stop each algorithm by calling @link iguana::Algorithm::Stop() `Stop()` @endlink Please let the maintainers know if your use case is not covered in any examples or if you need any help. +@anchor mainpageFlowchart +### Flowchart for Usage + Here is a flowchart, illustrating the above: - +


@@ -70,9 +76,9 @@ An Iguana algorithm is a function that maps input HIPO bank data to output data. | Type | Description | Example | | --- | --- | --- | -| **Filter** | Filters rows of a bank based on a Boolean condition | `iguana::clas12::FiducialFilter`: filter particles with fiducial cuts | -| **Transformer** | Transform (mutate) elements of a bank | `iguana::clas12::MomentumCorrection`: correct particle momenta | -| **Creator** | Create a new bank | `iguana::physics::InclusiveKinematics`: calculate inclusive kinematics @latex{x}, @latex{Q^2}, _etc_. | +| **Filter** | Filters rows of a bank based on a Boolean condition | @link iguana::clas12::FiducialFilter @endlink: filter particles with fiducial cuts | +| **Transformer** | Transform (mutate) elements of a bank | @link iguana::clas12::MomentumCorrection @endlink: correct particle momenta | +| **Creator** | Create a new bank | @link iguana::physics::InclusiveKinematics @endlink: calculate inclusive kinematics @latex{x}, @latex{Q^2}, _etc_. | The available algorithms are: @@ -85,7 +91,7 @@ The available algorithms are: @anchor mainpageRunning ## How to Run Algorithms -Algorithms may be run using either: +Algorithms may be run using one of the following; see [the usage flowchart](#mainpageFlowchart) for help deciding what to do. - [Common Functions](#mainpageCommon): for users of the [**the HIPO API**](https://github.com/gavalian/hipo), which is likely the case if you are using C++, _e.g._, via `clas12root` - [Action Functions](#mainpageAction): for all other users @@ -102,22 +108,30 @@ All algorithms have the following **Common Functions**, which may be used in ana | Common Functions || | --- | --- | -| `Start(hipo::banklist&)` | To be called before event processing | -| `Run(hipo::banklist&)` | To be called for every event | -| `Stop()` | To be called after event processing | - -The algorithms are implemented in C++ classes which inherit from the base class `iguana::Algorithm`; these three class methods are overridden in each algorithm. - -The `Run(hipo::banklist&)` function should be called on every event; the general consequence, that is, how the user should handle the algorithm's results, depends on the +| `Start` | To be called before event processing | +| `Run` | To be called for every event | +| `Stop` | To be called after event processing | + +- for `Start`: + - call @link iguana::Algorithm::Start() `Start()` @endlink if you will be using individual `hipo::bank` objects (_e.g._, if you are using `clas12root`) + - call @link iguana::Algorithm::Start(hipo::banklist&) `Start(hipo::banklist&)` @endlink if you use `hipo::banklist` + - **Tip:** `hipo::banklist` users may use @link iguana::AlgorithmSequence `AlgorithmSequence` @endlink to help run a _sequence_ of algorithms +- for `Run`: + - call specialized `Run` functions, which act on individual `hipo::bank` objects and are unique for each algorithm; users of `clas12root` versions _newer_ than `1.8.6` should use this + - call @link iguana::Algorithm::Run(hipo::banklist&) const `Run(hipo::banklist&)` @endlink if you use `hipo::banklist` +- the `Stop` function is the same for either case + +The `Run` function should be called on every event; the general consequence, that is, how the user should handle the algorithm's results, depends on the algorithm type:
Type How to handle the results
**Filter** -The involved banks will be filtered. -To iterate over _filtered_ bank rows, use the function `hipo::bank::getRowList`, -rather than iterating from `0` up to `hipo::bank::getRows()`; for example, given a -bank object named `particle_bank`: +The involved banks will be filtered. To iterate over _filtered_ bank rows, use +the function `hipo::bank::getRowList`, rather than iterating from `0` up to +`hipo::bank::getRows()`. +
+For example, given a bank object named `particle_bank`: ```cpp for(auto const& row : particle_bank.getRowList()) { // loops over only the rows which pass the filter @@ -130,12 +144,12 @@ for(int row = 0; row < particle_bank.getRows(); row++) { ```
**Transformer** -The transformed `hipo::bank` will simply have -the relevant bank elements changed. For example, momentum correction algorithms -typically change the particle momentum components. +The transformed `hipo::bank` will simply have the relevant bank elements changed. +
+For example, momentum correction algorithms typically change the particle momentum components.
**Creator** -Creator-type algorithms will simply create a new `hipo::bank` object, appending +Creator-type algorithms will create a new `hipo::bank` object, appending it to the end of the input `hipo::banklist`.