diff --git a/CODEOWNERS b/CODEOWNERS index 266912123..dca59b932 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -6,6 +6,8 @@ # @RichCap Richard Capobianco # @rtysonCLAS12 Richard Tyson # @Gregtom3 Gregory Matousek +# @MarianaT27 Mariana Tenorio Pita +# @tbhayward Timothy Hayward ################################################################################# * @c-dilks @@ -13,6 +15,7 @@ src/iguana/algorithms/clas12/CalorimeterLinker/* @c-dilks src/iguana/algorithms/clas12/EventBuilderFilter/* @c-dilks src/iguana/algorithms/clas12/FTEnergyCorrection/* @asligonulacar src/iguana/algorithms/clas12/FiducialFilter/* @Gregtom3 +src/iguana/algorithms/clas12/LeptonIDFilter/* @MarianaT27 src/iguana/algorithms/clas12/MatchParticleProximity/* @c-dilks src/iguana/algorithms/clas12/MomentumCorrection/* @RichCap @c-dilks src/iguana/algorithms/clas12/PhotonGBTFilter/* @Gregtom3 diff --git a/examples/meson.build b/examples/meson.build index 8ba5bb5c0..d3b616292 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -1,5 +1,5 @@ # install config files -example_config_files_prefix = project_etcdir / 'examples' +example_config_files_prefix = project_etc_dir / 'examples' install_subdir('config', install_dir: example_config_files_prefix, strip_directory: true) # example source information diff --git a/meson.build b/meson.build index 28d21364d..368fc4b50 100644 --- a/meson.build +++ b/meson.build @@ -9,6 +9,7 @@ project( 'cpp_std': 'c++17', 'buildtype': 'release', 'libdir': 'lib', + 'datadir': 'share', 'licensedir': 'share/licenses/iguana', 'pkgconfig.relocatable': 'true', 'force_fallback_for': ['rcdb'], @@ -20,6 +21,14 @@ project_description = 'Implementation Guardian of Analysis Algorithms' add_languages('fortran', native: false, required: get_option('bind_fortran')) use_chameleon = get_option('bind_fortran') +# check built-in build options +if get_option('libdir') != 'lib' + warning('build option "libdir" = "' + get_option('libdir') + '", which is not "lib"; if you experience any issues, please report them') +endif +if get_option('datadir') != 'share' + error('build option "datadir" must be "share", but it is set to "' + get_option('datadir') + '"') +endif + # warn that macOS is no longer tested if host_machine.system() == 'darwin' warning('''host machine system is darwin: @@ -70,6 +79,7 @@ ROOT_dep = dependency( 'ROOT::RIO', 'ROOT::ROOTDataFrame', 'ROOT::ROOTVecOps', + 'ROOT::TMVA', 'ROOT::TreePlayer', ], ) @@ -154,7 +164,8 @@ hipo_dep_dataframes_found = hipo_dep.get_variable( project_inc = include_directories('src') project_libs = [] project_deps = declare_dependency(dependencies: dep_list) -project_etcdir = get_option('sysconfdir') / meson.project_name() +project_etc_dir = get_option('sysconfdir') / meson.project_name() +project_data_dir = get_option('datadir') / meson.project_name() project_test_env = environment() project_pythondir = 'python' @@ -180,9 +191,14 @@ project_test_env.set( ) # set preprocessor macros -add_project_arguments('-DIGUANA_ETCDIR="' + get_option('prefix') / project_etcdir + '"', language: [ 'cpp' ]) +add_project_arguments( + '-D IGUANA_PREFIX="' + get_option('prefix') + '"', + '-D IGUANA_ETCDIR="' + project_etc_dir + '"', + '-D IGUANA_DATADIR="' + project_data_dir + '"', + language: ['cpp'], +) if ROOT_dep.found() - add_project_arguments('-DIGUANA_ROOT_FOUND', language: [ 'cpp' ]) # currently only used for Validator plot styles + add_project_arguments('-D IGUANA_ROOT_FOUND', language: [ 'cpp' ]) # currently only used for Validator plot styles endif # start chameleon @@ -236,7 +252,7 @@ if get_option('z_install_envfile') 'libdir': get_option('libdir'), 'includedir': get_option('includedir'), 'bindir': get_option('bindir'), - 'etcdir': project_etcdir, + 'etcdir': project_etc_dir, 'pythondir': project_pythondir, 'dep_pkgconfigdirs': ':'.join(dep_pkgconfig_dirs), 'dep_libdirs': ':'.join(dep_lib_dirs), diff --git a/meson/lsan.supp b/meson/lsan.supp index 0bf7473ac..f62dc34b2 100644 --- a/meson/lsan.supp +++ b/meson/lsan.supp @@ -1 +1,2 @@ leak:libCling.so +leak:TMVA::DataSetFactory::BuildDynamicDataSet diff --git a/src/iguana/algorithms/Algorithm.cc b/src/iguana/algorithms/Algorithm.cc index 3eb3276e8..96e1f5e49 100644 --- a/src/iguana/algorithms/Algorithm.cc +++ b/src/iguana/algorithms/Algorithm.cc @@ -86,6 +86,19 @@ namespace iguana { /////////////////////////////////////////////////////////////////////////////// + YAML::Node Algorithm::GetOptionNode(YAMLReader::node_path_t node_path) const + { + CompleteOptionNodePath("", node_path); + auto node = m_yaml_config->GetNode(node_path); + if(!node.has_value()) { + m_log->Error("Algorithm::GetOptionNode failed to find node"); + throw std::runtime_error("config file parsing issue"); + } + return node.value(); + } + + /////////////////////////////////////////////////////////////////////////////// + void Algorithm::SetName(std::string_view name) { Object::SetName(name); @@ -123,6 +136,17 @@ namespace iguana { /////////////////////////////////////////////////////////////////////////////// + std::string Algorithm::GetDataFile(std::string const& name) + { + if(!m_datafile_reader) { + m_datafile_reader = std::make_unique(ConfigFileReader::ConvertAlgoNameToConfigDir(m_class_name), "data|" + m_name); + m_datafile_reader->SetLogLevel(m_log->GetLevel()); + } + return m_datafile_reader->FindFile(name); + } + + /////////////////////////////////////////////////////////////////////////////// + void Algorithm::ParseYAMLConfig() { diff --git a/src/iguana/algorithms/Algorithm.h b/src/iguana/algorithms/Algorithm.h index f48049a1a..dfb134e18 100644 --- a/src/iguana/algorithms/Algorithm.h +++ b/src/iguana/algorithms/Algorithm.h @@ -8,9 +8,10 @@ #include "AlgorithmBoilerplate.h" #include "iguana/bankdefs/BankDefs.h" +#include "iguana/services/DataFileReader.h" +#include "iguana/services/GlobalParam.h" #include "iguana/services/RCDBReader.h" #include "iguana/services/YAMLReader.h" -#include namespace iguana { @@ -144,6 +145,11 @@ namespace iguana { template std::set GetOptionSet(std::string const& key, YAMLReader::node_path_t node_path = {}) const; + /// Get the `YAML::Node` for an option + /// @param node_path the `YAML::Node` identifier path to search + /// @returns the `YAML::Node` + YAML::Node GetOptionNode(YAMLReader::node_path_t node_path) const; + /// Set the name of this algorithm /// @param name the new name void SetName(std::string_view name); @@ -166,6 +172,11 @@ namespace iguana { /// @param name the directory name void SetConfigDirectory(std::string const& name); + /// Get the full path to a data file, such as a machine-learning model + /// @param name the name of the file; if found in the user's current working directory (`./`), that will be the file that is used; + /// otherwise the _installed_ file (in `$IGUANA/share/`) will be used by default + std::string GetDataFile(std::string const& name); + /// Get the index of a bank in a `hipo::banklist`; throws an exception if the bank is not found /// @param banks the list of banks this algorithm will use /// @param bank_name the name of the bank @@ -311,6 +322,9 @@ namespace iguana { /// Data structure to hold configuration options set by `Algorithm::SetOption` std::unordered_map m_option_cache; + + /// Data file reader + std::unique_ptr m_datafile_reader; }; ////////////////////////////////////////////////////////////////////////////// diff --git a/src/iguana/algorithms/clas12/LeptonIDFilter/Algorithm.cc b/src/iguana/algorithms/clas12/LeptonIDFilter/Algorithm.cc new file mode 100644 index 000000000..2e9ee10d9 --- /dev/null +++ b/src/iguana/algorithms/clas12/LeptonIDFilter/Algorithm.cc @@ -0,0 +1,207 @@ +#include "Algorithm.h" + +#include +#include + +namespace iguana::clas12 { + + REGISTER_IGUANA_ALGORITHM(LeptonIDFilter, "clas12::LeptonIDFilter"); + + void LeptonIDFilter::Start(hipo::banklist& banks) + { + // Get configuration + ParseYAMLConfig(); + o_pids = GetOptionSet("pids"); + o_cut = GetOptionScalar("cut"); + o_tmva_reader_options = GetOptionScalar("tmva_reader_options"); + o_particle_bank = GetOptionScalar("particle_bank"); + o_runnum = ConcurrentParamFactory::Create(); + o_weightfile_electron = ConcurrentParamFactory::Create(); + o_weightfile_positron = ConcurrentParamFactory::Create(); + + // Get Banks that we are going to use + b_particle = GetBankIndex(banks, o_particle_bank); + b_calorimeter = GetBankIndex(banks, "REC::Calorimeter"); + b_config = GetBankIndex(banks, "RUN::config"); + + // Initialize the TMVA reader + readerTMVA = std::make_unique(LeptonIDVars::names, o_tmva_reader_options); + + // find all the unique weights files in the configuration YAML + std::set weightfile_list; + for(auto const& node : GetOptionNode({"weightfile"})) { + for(std::string const particle : {"electron", "positron"}) { + if(node[particle]) { + weightfile_list.insert(node[particle].as()); + } + } + } + + // book the weights files + // use the name of weightfile as the method tag, for simplicity + m_log->Debug("Booking weight files:"); + for(auto const& weightfile_name : weightfile_list) { + auto weightfile_path = GetDataFile(weightfile_name); + m_log->Debug(" - {}", weightfile_path); + readerTMVA->BookMVA(weightfile_name, weightfile_path); + } + } + + + bool LeptonIDFilter::Run(hipo::banklist& banks) const + { + return Run( + GetBank(banks, b_particle, o_particle_bank), + GetBank(banks, b_calorimeter, "REC::Calorimeter"), + GetBank(banks, b_config, "RUN::config")); + } + + + bool LeptonIDFilter::Run(hipo::bank& particleBank, hipo::bank const& calorimeterBank, hipo::bank const& configBank) const + { + // particle bank before filtering + ShowBank(particleBank, Logger::Header("INPUT PARTICLES")); + + // prepare the event, reloading configuration parameters if the run number changed or is not yet known + auto key = PrepareEvent(configBank.getInt("run", 0)); + + // filter the particle bank + particleBank.getMutableRowList().filter([this, &calorimeterBank, key](auto bank, auto row) { + auto pid = bank.getInt("pid", row); + // check if this is a lepton in `o_pids` + if(o_pids.find(pid) != o_pids.end()) { + auto status = bank.getShort("status", row); + // status cut + if(std::abs(status) >= 2000 && std::abs(status) < 4000) { + m_log->Trace("Found lepton: pindex={}", row); + auto lepton_vars = GetLeptonIDVariables(row, bank, calorimeterBank); + lepton_vars.pid = pid; + lepton_vars.score = CalculateScore(lepton_vars, key); + return Filter(lepton_vars.score) ? 1 : 0; + } + else { + m_log->Trace("Lepton at pindex={} did not pass status cut", row); + return 0; + } + } + return 1; // not a lepton in `o_pids`, let it pass the filter + }); + + // particle bank after filtering + ShowBank(particleBank, Logger::Header("OUTPUT PARTICLES")); + return !particleBank.getRowList().empty(); + } + + + concurrent_key_t LeptonIDFilter::PrepareEvent(int const runnum) const + { + m_log->Trace("calling PrepareEvent({})", runnum); + if(o_runnum->NeedsHashing()) { + std::hash hash_ftn; + auto hash_key = hash_ftn(runnum); + if(!o_runnum->HasKey(hash_key)) + Reload(runnum, hash_key); + return hash_key; + } + else { + if(o_runnum->IsEmpty() || o_runnum->Load(0) != runnum) + Reload(runnum, 0); + return 0; + } + } + + + void LeptonIDFilter::Reload(int const runnum, concurrent_key_t key) const + { + std::lock_guard const lock(m_mutex); // NOTE: be sure to lock successive `ConcurrentParam::Save` calls !!! + m_log->Trace("-> calling Reload({}, {})", runnum, key); + o_runnum->Save(runnum, key); + o_weightfile_electron->Save(GetOptionScalar("weightfile:electron", {"weightfile", GetConfig()->InRange("runs", runnum), "electron"}), key); + o_weightfile_positron->Save(GetOptionScalar("weightfile:positron", {"weightfile", GetConfig()->InRange("runs", runnum), "positron"}), key); + } + + + LeptonIDVars LeptonIDFilter::GetLeptonIDVariables(int const plepton, hipo::bank const& particle_bank, hipo::bank const& calorimeter_bank) const + { + + double px = particle_bank.getFloat("px", plepton); + double py = particle_bank.getFloat("py", plepton); + double pz = particle_bank.getFloat("pz", plepton); + double E = std::sqrt(std::pow(px, 2) + std::pow(py, 2) + std::pow(pz, 2) + std::pow(0.000511, 2)); + ROOT::Math::PxPyPzMVector vec_lepton(px, py, pz, E); + + LeptonIDVars lepton; + + lepton.P = vec_lepton.P(); + lepton.Theta = vec_lepton.Theta(); + lepton.Phi = vec_lepton.Phi(); + + m_log->Debug("Variables obtained from particle bank"); + + + lepton.m2pcal = -1; + lepton.m2ecin = -1; + lepton.m2ecout = -1; + + for(int row = 0; row < calorimeter_bank.getRows(); row++) { + auto pindex = calorimeter_bank.getShort("pindex", row); + auto layer = calorimeter_bank.getByte("layer", row); + auto energy = calorimeter_bank.getFloat("energy", row); + auto m2u = calorimeter_bank.getFloat("m2u", row); + auto m2v = calorimeter_bank.getFloat("m2v", row); + auto m2w = calorimeter_bank.getFloat("m2w", row); + + if(pindex == plepton && layer == 1) { + lepton.SFpcal = energy / vec_lepton.P(); + lepton.m2pcal = (m2u + m2v + m2w) / 3; + } + + if(pindex == plepton && layer == 4) { + lepton.SFecin = energy / vec_lepton.P(); + lepton.m2ecin = (m2u + m2v + m2w) / 3; + } + if(pindex == plepton && layer == 7) { + lepton.SFecout = energy / vec_lepton.P(); + lepton.m2ecout = (m2u + m2v + m2w) / 3; + } + } + + + m_log->Debug("Variables obtained from calorimeter bank"); + + return lepton; + } + + + double LeptonIDFilter::CalculateScore(LeptonIDVars lepton_vars, concurrent_key_t const key) const + { + // Assigning variables from lepton_vars for TMVA method + std::string weightsfile; + switch(lepton_vars.pid) { + case 11: + weightsfile = o_weightfile_electron->Load(key); + break; + case -11: + weightsfile = o_weightfile_positron->Load(key); + break; + default: + throw std::runtime_error(fmt::format("unknown lepton PDG code {}", lepton_vars.pid)); + } + return readerTMVA->EvaluateMVA(lepton_vars.GetValues(), weightsfile); + } + + + bool LeptonIDFilter::Filter(double score) const + { + if(score >= o_cut) + return true; + else + return false; + } + + + void LeptonIDFilter::Stop() + { + } + +} diff --git a/src/iguana/algorithms/clas12/LeptonIDFilter/Algorithm.h b/src/iguana/algorithms/clas12/LeptonIDFilter/Algorithm.h new file mode 100644 index 000000000..715337602 --- /dev/null +++ b/src/iguana/algorithms/clas12/LeptonIDFilter/Algorithm.h @@ -0,0 +1,139 @@ +#pragma once + +#include "iguana/algorithms/Algorithm.h" +#include "iguana/services/ConcurrentParam.h" +#include + +/// Struct to store variables +struct LeptonIDVars { + /// @brief momentum + Double_t P; + /// @brief Theta angle + Double_t Theta; + /// @brief Phi angle + Double_t Phi; + /// @brief Sampling fraction on the PCAL + Double_t SFpcal; + /// @brief Sampling fraction on the ECIN + Double_t SFecin; + /// @brief Sampling fraction on the ECOUT + Double_t SFecout; + /// @brief Second-momenta of PCAL + Double_t m2pcal; + /// @brief Second-momenta of ECIN + Double_t m2ecin; + /// @brief Second-momenta of ECOUT + Double_t m2ecout; + /// @brief Score + Double_t score; + /// @brief PDG code + Int_t pid; + + /// @return list of variable values, to pass to `TMVA::Reader::EvaluateMVA` + std::vector GetValues() + { + return { // NOTE: order must be consistent with `names` + P, + Theta, + Phi, + SFpcal, + SFecin, + SFecout, + m2pcal, + m2ecin, + m2ecout, + }; + } + + /// list of variable names, to pass to `TMVA::Reader` constructor + inline static std::vector names = { // NOTE: order must be consistent with `GetValues` + "P", + "Theta", + "Phi", + "SFPCAL", + "SFECIN", + "SFECOUT", + "m2PCAL", + "m2ECIN", + "m2ECOUT", + }; +}; + +namespace iguana::clas12 { + /// @algo_brief{Filter the leptons from the pion contamination using TMVA models} + /// @algo_type_filter + /// + /// For each lepton, either positron or electron, it takes some variables from `REC::Particle` (P, Theta and Phi) and `REC::Particle` (Sampling fraction and second moments). + /// Using those variables, it call the TMVA method using the weight file, and it computes a score. By a pplying a cut to the score we can separate leptons from pions. + /// + /// @begin_doc_config{clas12/LeptonIDFilter} + /// @config_param{pids | int | PIDs of the particle; -11 for positrons and 11 for electrons} + /// @config_param{weightfile | string | Location of the weight file of the classifier} + /// @config_param{cut | double | Value of the score to apply the cut. The algorithm will keep all particles that have a score grater than ths value} + /// @end_doc + class LeptonIDFilter : public Algorithm + { + + DEFINE_IGUANA_ALGORITHM(LeptonIDFilter, clas12::LeptonIDFilter) + + public: + + void Start(hipo::banklist& banks) override; + bool Run(hipo::banklist& banks) const override; + void Stop() override; + + /// @run_function + /// @param [in,out] particleBank particle bank (_viz._, `REC::Particle`), which will be filtered + /// @param [in] calorimeterBank `REC::Calorimeter` bank + /// @param [in] configBank `RUN::config` bank + /// @returns `false` if all particles are filtered out + bool Run(hipo::bank& particleBank, hipo::bank const& calorimeterBank, hipo::bank const& configBank) const; + + /// @action_function{reload} prepare the event + /// @when_to_call{for each event} + /// @param runnum the run number + /// @returns the key to be used in `::Filter` + concurrent_key_t PrepareEvent(int const runnum) const; + + /// @brief Using the pindex, retrieves the necessary variables from banks + /// @param plepton pindex of the lepton + /// @param particle_bank the particle bank + /// @param calorimeter_bank the calorimeter bank + /// @returns LeptonIDVars, the variables required for identification + LeptonIDVars GetLeptonIDVariables(int const plepton, hipo::bank const& particle_bank, hipo::bank const& calorimeter_bank) const; + + /// @brief Using the LeptonIDVars, variables calculate the score + /// @param lepton_vars LeptonIDVars variables + /// @param key the return value of `::PrepareEvent` + /// @returns double, the score + double CalculateScore(LeptonIDVars lepton_vars, concurrent_key_t const key) const; + + /// @brief Returns true if the particle passed the cut + /// @param score the score obtained from the CalculateScore function + /// @returns bool, true if score>=cut, false otherwise + bool Filter(double score) const; + + private: + + // Reload function + void Reload(int const runnum, concurrent_key_t key) const; + + /// TMVA reader + std::unique_ptr readerTMVA; + + /// `hipo::banklist` + hipo::banklist::size_type b_particle; + hipo::banklist::size_type b_calorimeter; + hipo::banklist::size_type b_config; + + // config options + std::set o_pids; + mutable std::unique_ptr> o_runnum; + mutable std::unique_ptr> o_weightfile_electron; + mutable std::unique_ptr> o_weightfile_positron; + double o_cut; + std::string o_tmva_reader_options; + std::string o_particle_bank; + }; + +} diff --git a/src/iguana/algorithms/clas12/LeptonIDFilter/Config.yaml b/src/iguana/algorithms/clas12/LeptonIDFilter/Config.yaml new file mode 100644 index 000000000..1e400882a --- /dev/null +++ b/src/iguana/algorithms/clas12/LeptonIDFilter/Config.yaml @@ -0,0 +1,48 @@ +clas12::LeptonIDFilter: + + # choose which lepton PDG codes to filter + pids: [-11, 11] + + # weight file, one for each lepton and dataset + weightfile: + # + # + # FIXME: double check these run number ranges + # FIXME: use the correct weights files for each + # FIXME: warn the user if run number falls in 'default' data set + # + # + - default: # run number is not in any other data set below + electron: 'weights/9_BDT_positrons_S19.weights.xml' + positron: 'weights/9_BDT_positrons_S19.weights.xml' + - runs: [3211, 3293] # RG-A Spring 2018 Outbending Part 1 + electron: 'weights/9_BDT_positrons_S19.weights.xml' + positron: 'weights/9_BDT_positrons_S19.weights.xml' + - runs: [3306, 3817] # RG-A Spring 2018 Inbending Part 1 + electron: 'weights/9_BDT_positrons_S19.weights.xml' + positron: 'weights/9_BDT_positrons_S19.weights.xml' + - runs: [3863, 3987] # RG-A Spring 2018 Outbending Part 2 + electron: 'weights/9_BDT_positrons_S19.weights.xml' + positron: 'weights/9_BDT_positrons_S19.weights.xml' + - runs: [4003, 4325] # RG-A Spring 2018 Inbending Part 2 + electron: 'weights/9_BDT_positrons_S19.weights.xml' + positron: 'weights/9_BDT_positrons_S19.weights.xml' + - runs: [5032, 5419] # RG-A Fall 2018 Inbending + electron: 'weights/9_BDT_positrons_S19.weights.xml' + positron: 'weights/9_BDT_positrons_S19.weights.xml' + - runs: [5422, 5666] # RG-A Fall 2018 Outbending + electron: 'weights/9_BDT_positrons_S19.weights.xml' + positron: 'weights/9_BDT_positrons_S19.weights.xml' + - runs: [6616, 6783] # RG-A Spring 2019 Inbending + electron: 'weights/9_BDT_positrons_S19.weights.xml' + positron: 'weights/9_BDT_positrons_S19.weights.xml' + + # value of the score to apply the cut + cut: 0.0 + + # option string to pass to `TMVA::Reader` constructor, + # e.g., "V" to set the "Verbose flag" + tmva_reader_options: '' + + # the name of the particle bank, only needed for users of `hipo::banklist` + particle_bank: 'REC::Particle' diff --git a/src/iguana/algorithms/clas12/LeptonIDFilter/weights/9_BDT_positrons_S19.weights.xml b/src/iguana/algorithms/clas12/LeptonIDFilter/weights/9_BDT_positrons_S19.weights.xml new file mode 100644 index 000000000..f4b4ac60c --- /dev/null +++ b/src/iguana/algorithms/clas12/LeptonIDFilter/weights/9_BDT_positrons_S19.weights.xml @@ -0,0 +1,13538 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/iguana/algorithms/meson.build b/src/iguana/algorithms/meson.build index aaddac35a..a5de54df8 100644 --- a/src/iguana/algorithms/meson.build +++ b/src/iguana/algorithms/meson.build @@ -12,6 +12,7 @@ # 'validator_needs_ROOT': bool # whether this validator needs ROOT or not (default=true) # 'add_algorithm_sources': list[str] # list of additional algorithm source files (default=[]) # 'add_algorithm_headers': list[str] # list of additional algorithm header files (default=[]) +# 'add_algorithm_data': list[str] # list of additional files to install (default=[]) # 'add_validator_sources': list[str] # list of additional validator source files (default=[]) # 'add_validator_headers': list[str] # list of additional validator header files (default=[]) # 'test_args': { dict[str], # if excluded, tests won't run for this algorithm @@ -129,6 +130,16 @@ algo_dict = [ 'has_action_yaml': false, 'test_args': {'banks': [ 'REC::Particle', 'REC::Calorimeter', 'RUN::config' ]}, }, + { + 'name': 'clas12::LeptonIDFilter', + 'algorithm_needs_ROOT': true, + 'has_validator': false, + 'has_action_yaml': false, + 'add_algorithm_data': [ + 'weights/9_BDT_positrons_S19.weights.xml', + ], + 'test_args': { 'banks': ['REC::Particle', 'REC::Calorimeter', 'RUN::config'] }, + }, { 'name': 'clas12::rga::FiducialFilterPass2', 'has_validator': true, @@ -170,6 +181,7 @@ if ROOT_dep.found() endif vdor_sources = [ 'Validator.cc' ] vdor_headers = [ 'Validator.h' ] +algo_data = [] algo_configs = [] algo_bind_c_sources = [] @@ -186,7 +198,7 @@ foreach algo : algo_dict # algorithms: if (algo_needs_ROOT and ROOT_dep.found()) or not algo_needs_ROOT - # sources and headers + # sources, headers, and data algo_sources += algo_dir / 'Algorithm.cc' algo_headers += algo_dir / 'Algorithm.h' foreach src_file : algo.get('add_algorithm_sources', []) @@ -195,6 +207,9 @@ foreach algo : algo_dict foreach src_file : algo.get('add_algorithm_headers', []) algo_headers += algo_dir / src_file endforeach + foreach src_file : algo.get('add_algorithm_data', []) + algo_data += algo_dir / src_file + endforeach # config files if algo_has_config @@ -250,6 +265,13 @@ algo_lib = shared_library( install: true, ) install_headers(algo_headers, subdir: meson.project_name() / 'algorithms', preserve_path: true) +foreach algo_datum : algo_data + install_data( + algo_data, + install_dir: project_data_dir / 'algorithms', + preserve_path: true, + ) +endforeach project_libs += algo_lib # build and install validators @@ -265,5 +287,5 @@ install_headers(vdor_headers, subdir: meson.project_name() / 'algorithms', prese # install config files foreach algo_config : algo_configs - install_data(algo_config, install_dir: project_etcdir / 'algorithms', preserve_path: true) + install_data(algo_config, install_dir: project_etc_dir / 'algorithms', preserve_path: true) endforeach diff --git a/src/iguana/bankdefs/meson.build b/src/iguana/bankdefs/meson.build index 720a82bdd..81d0dd452 100644 --- a/src/iguana/bankdefs/meson.build +++ b/src/iguana/bankdefs/meson.build @@ -17,4 +17,4 @@ bankdef_tgt = custom_target( ) # install the JSON data model file; iguana won't need it, but it can be useful for user reference -install_data(bankdef_json, install_dir: project_etcdir / 'bankdefs' / 'hipo4') +install_data(bankdef_json, install_dir: project_etc_dir / 'bankdefs' / 'hipo4') diff --git a/src/iguana/services/ConfigFileReader.cc b/src/iguana/services/ConfigFileReader.cc index e391d5a08..e42a6656c 100644 --- a/src/iguana/services/ConfigFileReader.cc +++ b/src/iguana/services/ConfigFileReader.cc @@ -6,35 +6,37 @@ namespace iguana { - ConfigFileReader::ConfigFileReader(std::string_view name) + ConfigFileReader::ConfigFileReader(std::string_view name, bool set_default_dirs) : Object(name) { - // the algorithms need to know where the config files are; - // first, add config files from installation prefix; since this - // is added first, it's the lowest priority in the configuration - // search path - AddDirectory(GetConfigInstallationPrefix()); - // next, add `IGUANA_CONFIG_PATH` paths, which provides: - // - user override of the configuration path(s) - // - a fallback, if `GetConfigInstallationPrefix` is wrong, which happens - // if the Iguana installation is relocated - auto user_paths_env_var = std::getenv("IGUANA_CONFIG_PATH"); - if(user_paths_env_var != nullptr) { - std::istringstream user_paths_stream(user_paths_env_var); - std::string user_path_token; - decltype(m_directories) user_paths; - while(getline(user_paths_stream, user_path_token, ':')) // tokenize `IGUANA_CONFIG_PATH` - user_paths.push_front(user_path_token); - for(auto const& user_path : user_paths) // add the paths in the correct order - AddDirectory(user_path); - // after these default paths have been added, the user - // may still override by calling `AddDirectory` etc. themselves + if(set_default_dirs) { + // the algorithms need to know where the config files are; + // first, add config files from installation prefix; since this + // is added first, it's the lowest priority in the configuration + // search path + AddDirectory(GetConfigInstallationPrefix()); + // next, add `IGUANA_CONFIG_PATH` paths, which provides: + // - user override of the configuration path(s) + // - a fallback, if `GetConfigInstallationPrefix` is wrong, which happens + // if the Iguana installation is relocated + auto user_paths_env_var = std::getenv("IGUANA_CONFIG_PATH"); + if(user_paths_env_var != nullptr) { + std::istringstream user_paths_stream(user_paths_env_var); + std::string user_path_token; + decltype(m_directories) user_paths; + while(getline(user_paths_stream, user_path_token, ':')) // tokenize `IGUANA_CONFIG_PATH` + user_paths.push_front(user_path_token); + for(auto const& user_path : user_paths) // add the paths in the correct order + AddDirectory(user_path); + // after these default paths have been added, the user + // may still override by calling `AddDirectory` etc. themselves + } } } std::string ConfigFileReader::GetConfigInstallationPrefix() { - return IGUANA_ETCDIR; + return std::string(IGUANA_PREFIX) + "/" + std::string(IGUANA_ETCDIR); } void ConfigFileReader::AddDirectory(std::string const& dir) @@ -90,13 +92,18 @@ namespace iguana { throw std::runtime_error("configuration file not found"); } - std::string ConfigFileReader::ConvertAlgoNameToConfigName(std::string_view algo_name, std::string_view ext) + std::string ConfigFileReader::ConvertAlgoNameToConfigDir(std::string_view algo_name) { std::string result = std::string(algo_name); std::string::size_type it = 0; while((it = result.find("::", it)) != std::string::npos) result.replace(it, 2, "/"); - return "algorithms/" + result + "/Config." + std::string(ext); + return "algorithms/" + result; + } + + std::string ConfigFileReader::ConvertAlgoNameToConfigName(std::string_view algo_name, std::string_view ext) + { + return ConvertAlgoNameToConfigDir(algo_name) + "/Config." + std::string(ext); } } diff --git a/src/iguana/services/ConfigFileReader.h b/src/iguana/services/ConfigFileReader.h index e9bbde2ec..58f1044df 100644 --- a/src/iguana/services/ConfigFileReader.h +++ b/src/iguana/services/ConfigFileReader.h @@ -12,7 +12,8 @@ namespace iguana { public: /// @param name the name of this configuration file handler - ConfigFileReader(std::string_view name = "config"); + /// @param set_default_dirs if true, add the default configuration directories + ConfigFileReader(std::string_view name = "config", bool set_default_dirs = true); /// Get the config files' _fixed_ installation prefix /// @warning if the Iguana installation is _relocated_, this directory will **not** be correct, @@ -42,6 +43,11 @@ namespace iguana { /// @return the found configuration file (with the directory) std::string FindFile(std::string name); + /// Convert a full algorithm name to its corresponding default config file subdirectory + /// @param algo_name the algorithm name + /// @return the config file subdirectory + static std::string ConvertAlgoNameToConfigDir(std::string_view algo_name); + /// Convert a full algorithm name to its corresponding default config file name /// @param algo_name the algorithm name /// @param ext the file extension diff --git a/src/iguana/services/DataFileReader.cc b/src/iguana/services/DataFileReader.cc new file mode 100644 index 000000000..797bc954f --- /dev/null +++ b/src/iguana/services/DataFileReader.cc @@ -0,0 +1,16 @@ +#include "DataFileReader.h" + +namespace iguana { + DataFileReader::DataFileReader(std::string_view datadir_subdir, std::string_view name) + : ConfigFileReader(name, false) + { + // first, add the "fallback" directory; this is the lowest priority directory, relying + // on the environment variable '$IGUANA', in case the higher-priority, build-time path fails + auto user_prefix = std::getenv("IGUANA"); + if(user_prefix != nullptr) + AddDirectory(std::string(user_prefix) + "/" + std::string(IGUANA_DATADIR) + "/" + std::string(datadir_subdir)); + // then add the hard-coded path, which is set at build time; if Iguana installation is + // relocated, this path will be wrong and the above fallback will be used instead + AddDirectory(std::string(IGUANA_PREFIX) + "/" + std::string(IGUANA_DATADIR) + "/" + std::string(datadir_subdir)); + } +} diff --git a/src/iguana/services/DataFileReader.h b/src/iguana/services/DataFileReader.h new file mode 100644 index 000000000..11bb0f18b --- /dev/null +++ b/src/iguana/services/DataFileReader.h @@ -0,0 +1,18 @@ +#pragma once + +#include "ConfigFileReader.h" + +namespace iguana { + + /// @brief A data file reader, for example, weights files for machine learning-dependent algorithms + class DataFileReader : public ConfigFileReader + { + + public: + + /// @param datadir_subdir the subdirectory within build option `datadir` where the file may be found + /// @param name of this reader (for `Logger`) + DataFileReader(std::string_view datadir_subdir = "", std::string_view name = "data_file"); + ~DataFileReader() {} + }; +} diff --git a/src/iguana/services/YAMLReader.cc b/src/iguana/services/YAMLReader.cc index 37d301606..d0edd8c26 100644 --- a/src/iguana/services/YAMLReader.cc +++ b/src/iguana/services/YAMLReader.cc @@ -21,6 +21,18 @@ namespace iguana { /////////////////////////////////////////////////////////////////////////////// + std::optional YAMLReader::GetNode(node_path_t node_path) + { + for(auto const& [config, filename] : m_configs) { + auto node = FindNode(config, node_path); + if(node.IsDefined() && !node.IsNull()) + return node; + } + return std::nullopt; + } + + /////////////////////////////////////////////////////////////////////////////// + template std::optional YAMLReader::GetScalar(YAML::Node node) { @@ -46,12 +58,8 @@ namespace iguana { template std::optional YAMLReader::GetScalar(node_path_t node_path) { - for(auto const& [config, filename] : m_configs) { - auto node = FindNode(config, node_path); - if(node.IsDefined() && !node.IsNull()) - return GetScalar(node); - } - return std::nullopt; + auto node = GetNode(node_path); + return node ? GetScalar(node.value()) : std::nullopt; } template std::optional YAMLReader::GetScalar(node_path_t node_path); template std::optional YAMLReader::GetScalar(node_path_t node_path); @@ -87,12 +95,8 @@ namespace iguana { template std::optional> YAMLReader::GetVector(node_path_t node_path) { - for(auto const& [config, filename] : m_configs) { - auto node = FindNode(config, node_path); - if(node.IsDefined() && !node.IsNull()) - return GetVector(node); - } - return std::nullopt; + auto node = GetNode(node_path); + return node ? GetVector(node.value()) : std::nullopt; } template std::optional> YAMLReader::GetVector(node_path_t node_path); template std::optional> YAMLReader::GetVector(node_path_t node_path); diff --git a/src/iguana/services/YAMLReader.h b/src/iguana/services/YAMLReader.h index 79408d0d9..4f30713cf 100644 --- a/src/iguana/services/YAMLReader.h +++ b/src/iguana/services/YAMLReader.h @@ -36,6 +36,11 @@ namespace iguana { /// Parse the YAML files added by `ConfigFileReader::AddFile` void LoadFiles(); + /// Get a `YAML::Node`, given a `YAML::Node` path + /// @param node_path the `YAML::Node` path + /// @return the `YAML::Node`, if found + std::optional GetNode(node_path_t node_path); + /// Read a scalar value from a `YAML::Node` /// @param node the `YAML::Node` to read /// @return the scalar, if found diff --git a/src/iguana/services/meson.build b/src/iguana/services/meson.build index 86e93f742..35a9e3296 100644 --- a/src/iguana/services/meson.build +++ b/src/iguana/services/meson.build @@ -7,6 +7,7 @@ services_sources = [ 'GlobalParam.cc', 'RCDBReader.cc', 'Tools.cc', + 'DataFileReader.cc', ] services_headers = [ 'Logger.h', @@ -17,6 +18,7 @@ services_headers = [ 'GlobalParam.h', 'RCDBReader.h', 'Tools.h', + 'DataFileReader.h', ] if rcdb_dep.found()