diff --git a/external/.gitignore b/external/.gitignore index 95ce981061e9e672bcb76b01d85e66818d21b1b1..a0c5783cbd6a3c95168e0762783c305c6564d094 100644 --- a/external/.gitignore +++ b/external/.gitignore @@ -8,4 +8,4 @@ flib_dpb/flib_dpb ipc/ipc jsroot googletest - +yaml-cpp/ diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 1768203c4db82c927a0e7e470775a2c2d2bb2b20..298f0fc5bd86fe3cf916c19a0f646b61aef21b11 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -43,6 +43,8 @@ if(DOWNLOAD_EXTERNALS) Include(InstallParameter.cmake) Include(InstallInput.cmake) Include(InstallGeometry.cmake) + + Include(InstallYamlCpp.cmake) else() # Define targets which are needed by CbmRoot but are not available # whithout the external packages diff --git a/external/InstallYamlCpp.cmake b/external/InstallYamlCpp.cmake new file mode 100644 index 0000000000000000000000000000000000000000..6c8ff0cda742785c0c338ff05f16422c15c53128 --- /dev/null +++ b/external/InstallYamlCpp.cmake @@ -0,0 +1,44 @@ +set(YAMLCPP_VERSION 0579ae3d976091d7d664aa9d2527e0d0cff25763) # version 0.7.0 + +set(YAMLCPP_SRC_URL "https://github.com/jbeder/yaml-cpp") +set(YAMLCPP_DESTDIR "${CMAKE_BINARY_DIR}/external/YAMLCPP-prefix") + +#set(YAMLCPP_BYPRODUCT "${PROJECT_BINARY_DIR}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}yaml-cpp${CMAKE_SHARED_LIBRARY_SUFFIX}") + +download_project_if_needed(PROJECT yaml-cpp + GIT_REPOSITORY ${YAMLCPP_SRC_URL} + GIT_TAG ${YAMLCPP_VERSION} + SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/yaml-cpp + TEST_FILE CMakeLists.txt + ) + +If(ProjectUpdated) + File(REMOVE_RECURSE ${YAMLCPP_DESTDIR}) + Message("yaml-cpp source directory was changed so build directory was deleted") +EndIf() + +ExternalProject_Add( + yaml-cpp + SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/yaml-cpp + GIT_CONFIG advice.detachedHead=false + BUILD_IN_SOURCE 0 + LOG_DOWNLOAD 1 LOG_CONFIGURE 1 LOG_BUILD 1 LOG_INSTALL 1 + CMAKE_ARGS -DYAML_CPP_BUILD_CONTRIB=OFF + -DYAML_CPP_BUILD_TOOLS=OFF + -DYAML_CPP_BUILD_TESTS=OFF + -DYAML_BUILD_SHARED_LIBS=OFF + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + BUILD_COMMAND ${CMAKE_COMMAND} --build . --target yaml-cpp --parallel 1 + BUILD_BYPRODUCTS ${PROJECT_BINARY_DIR}/external/yaml-cpp-prefix/src/yaml-cpp-build/${CMAKE_STATIC_LIBRARY_PREFIX}yaml-cpp${CMAKE_STATIC_LIBRARY_SUFFIX} + INSTALL_COMMAND "" +) + +# pre-create empty directory to make INTERFACE_INCLUDE_DIRECTORIES happy +file(MAKE_DIRECTORY ${CMAKE_SOURCE_DIR}/external/yaml-cpp/include) + +add_library(external::yaml-cpp STATIC IMPORTED GLOBAL) +add_dependencies(external::yaml-cpp yaml-cpp) +set_target_properties(external::yaml-cpp PROPERTIES + IMPORTED_LOCATION ${PROJECT_BINARY_DIR}/external/yaml-cpp-prefix/src/yaml-cpp-build/${CMAKE_STATIC_LIBRARY_PREFIX}yaml-cpp${CMAKE_STATIC_LIBRARY_SUFFIX} + INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_SOURCE_DIR}/external/yaml-cpp/include +) diff --git a/reco/CMakeLists.txt b/reco/CMakeLists.txt index 51754b710c8ea438c15f8875a585bc0524b710fe..2df13eab8925ed3d772dee185cbf1e2f9e200b3b 100644 --- a/reco/CMakeLists.txt +++ b/reco/CMakeLists.txt @@ -14,4 +14,5 @@ add_subdirectory(tracking) add_subdirectory(qa) if (${CMAKE_CXX_STANDARD} EQUAL 17) add_subdirectory (tasks) + add_subdirectory (app) endif() diff --git a/reco/app/CMakeLists.txt b/reco/app/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..c81df5543f7b511317bfe6bf35bb78ebe481e215 --- /dev/null +++ b/reco/app/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(cbmreco_fairrun) diff --git a/reco/app/cbmreco_fairrun/Application.cxx b/reco/app/cbmreco_fairrun/Application.cxx new file mode 100644 index 0000000000000000000000000000000000000000..4c60fc4090fec9d4b4107e918ac4d515e0800870 --- /dev/null +++ b/reco/app/cbmreco_fairrun/Application.cxx @@ -0,0 +1,16 @@ +/* Copyright (C) 2022 Johann Wolfgang Goethe-Universitaet Frankfurt, Frankfurt am Main + SPDX-License-Identifier: GPL-3.0-only + Authors: Jan de Cuveland [committer] */ + +#include "Application.h" + +Application::Application(ProgramOptions const& opt) : fOpt(opt) +{ + CbmRecoConfig config; + config.LoadYaml(fOpt.ConfigYamlFile()); + if (!fOpt.SaveConfigYamlFile().empty()) { config.SaveYaml(fOpt.SaveConfigYamlFile()); } + + fCbmReco = std::make_unique<CbmReco>(fOpt.InputUri(), fOpt.OutputRootFile(), fOpt.MaxNumTs(), config); +} + +void Application::Run() { fCbmReco->Run(); } diff --git a/reco/app/cbmreco_fairrun/Application.h b/reco/app/cbmreco_fairrun/Application.h new file mode 100644 index 0000000000000000000000000000000000000000..9f877b9166f3af1fa4dd30676e0b938b25d13781 --- /dev/null +++ b/reco/app/cbmreco_fairrun/Application.h @@ -0,0 +1,43 @@ +/* Copyright (C) 2022 Johann Wolfgang Goethe-Universitaet Frankfurt, Frankfurt am Main + SPDX-License-Identifier: GPL-3.0-only + Authors: Jan de Cuveland [committer] */ + +#ifndef APP_CBMRECO_APPLICATION_H +#define APP_CBMRECO_APPLICATION_H + +#include "CbmReco.h" + +#include <memory> + +#include "ProgramOptions.h" +#include "log.hpp" + +/** @class Application + ** @brief Main class of the "cbmreco_fairrun" application + ** @author Jan de Cuveland <cuveland@compeng.uni-frankfurt.de> + ** @since 16 March 2022 + ** + ** This class implements a stand-alone command-line application. + ** It instantiatates and configures a CbmReco object, which executes + ** the CBM reconstruction steps using FairTasks and FairRunOnline. + **/ +class Application { +public: + /** @brief Standard constructor, initialize the application **/ + explicit Application(ProgramOptions const& opt); + + /** @brief Copy constructor forbidden **/ + Application(const Application&) = delete; + + /** @brief Assignment operator forbidden **/ + void operator=(const Application&) = delete; + + /** @brief Run the application **/ + void Run(); + +private: + ProgramOptions const& fOpt; ///< Program options object + std::unique_ptr<CbmReco> fCbmReco; ///< CBM reconstruction steering class instance +}; + +#endif diff --git a/reco/app/cbmreco_fairrun/CMakeLists.txt b/reco/app/cbmreco_fairrun/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..7d2068e4d5415f207dbd4f8fdae2232d279fb578 --- /dev/null +++ b/reco/app/cbmreco_fairrun/CMakeLists.txt @@ -0,0 +1,29 @@ +# Copyright (C) 2022 Johann Wolfgang Goethe-Universitaet Frankfurt, Frankfurt am Main +# SPDX-License-Identifier: GPL-3.0-only +# Authors: Jan de Cuveland [committer] + +file(GLOB APP_SOURCES *.cxx) +file(GLOB APP_HEADERS *.h) + +add_executable(cbmreco_fairrun ${APP_SOURCES} ${APP_HEADERS}) + +target_include_directories(cbmreco_fairrun SYSTEM PUBLIC ${Boost_INCLUDE_DIRS}) + +# This will no longer be necessary when the CbmRecoTasks library is set up to include this property +target_include_directories(cbmreco_fairrun + PUBLIC ${CMAKE_SOURCE_DIR}/reco/tasks +) + +# This will no longer be necessary when the Fairroot libraries are set up to include this property +target_link_directories(cbmreco_fairrun + PUBLIC ${FAIRROOT_LIBRARY_DIR} +) + +target_link_libraries(cbmreco_fairrun + fles_logging + CbmRecoTasks + Core + ${Boost_LIBRARIES} +) + +install(TARGETS cbmreco_fairrun DESTINATION bin) diff --git a/reco/app/cbmreco_fairrun/ProgramOptions.cxx b/reco/app/cbmreco_fairrun/ProgramOptions.cxx new file mode 100644 index 0000000000000000000000000000000000000000..ce629965462193485fa31f3e6923f9a54289c1af --- /dev/null +++ b/reco/app/cbmreco_fairrun/ProgramOptions.cxx @@ -0,0 +1,97 @@ +/* Copyright (C) 2022 Johann Wolfgang Goethe-Universitaet Frankfurt, Frankfurt am Main + SPDX-License-Identifier: GPL-3.0-only + Authors: Jan de Cuveland [committer] */ + +#include "ProgramOptions.h" + +#include <boost/program_options.hpp> + +#include <fstream> +#include <iostream> + +#include "log.hpp" + +namespace po = boost::program_options; + +void ProgramOptions::ParseOptions(int argc, char* argv[]) +{ + unsigned log_level = 2; + unsigned log_syslog = 2; + std::string log_file; + std::string options_file; + + // --- Define generic options + po::options_description generic("Generic options"); + auto generic_add = generic.add_options(); + generic_add("options-file,f", po::value<std::string>(&options_file)->value_name("<filename>"), + "read program options from file"); + generic_add("log-level,l", po::value<unsigned>(&log_level)->default_value(log_level)->value_name("<n>"), + "set the file log level (all:0)"); + generic_add("log-file,L", po::value<std::string>(&log_file)->value_name("<filename>"), "write log output to file"); + generic_add("log-syslog,S", po::value<unsigned>(&log_syslog)->implicit_value(log_syslog)->value_name("<n>"), + "enable logging to syslog at given log level"); + generic_add("help,h", "display this help and exit"); + generic_add("version,V", "output version information and exit"); + + // --- Define configuration options + po::options_description config("Configuration"); + auto config_add = config.add_options(); + config_add( + "input,i", + po::value<std::vector<std::string>>()->multitoken()->value_name("scheme://host/path?param=value ... | <filename>"), + "uri of a timeslice source"); + config_add("output-root,o", + po::value<std::string>(&fOutputRootFile)->default_value(fOutputRootFile)->value_name("<filename>"), + "name of an output root file to write"); + config_add("config,c", po::value<std::string>(&fConfigYamlFile)->value_name("<filename>"), + "name of a yaml config file containing the reconstruction chain configuration"); + config_add("save-config", po::value<std::string>(&fSaveConfigYamlFile)->value_name("<filename>"), + "save configuration to yaml file (mostly for debugging)"); + config_add("max-timeslice-number,n", po::value<int32_t>(&fMaxNumTs)->value_name("<n>"), + "quit after processing given number of timeslices (default: unlimited)"); + + po::options_description cmdline_options("Allowed options"); + cmdline_options.add(generic).add(config); + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, cmdline_options), vm); + po::notify(vm); + + // --- Read program options from file if requested + if (!options_file.empty()) { + std::ifstream ifs(options_file.c_str()); + if (!ifs) { throw ProgramOptionsException("cannot open options file: " + options_file); } + po::store(po::parse_config_file(ifs, config), vm); + notify(vm); + } + + if (vm.count("help") != 0u) { + std::cout << cmdline_options << std::endl; + exit(EXIT_SUCCESS); + } + + if (vm.count("version") != 0u) { + std::cout << "cbmreco, version (unspecified)" << std::endl; + exit(EXIT_SUCCESS); + } + + // --- Set up logging + logging::add_console(static_cast<severity_level>(log_level)); + if (vm.count("log-file") != 0u) { + L_(info) << "Logging output to " << log_file; + logging::add_file(log_file, static_cast<severity_level>(log_level)); + } + if (vm.count("log-syslog") != 0u) { + logging::add_syslog(logging::syslog::local0, static_cast<severity_level>(log_syslog)); + } + + if (vm.count("input") == 0u) { throw ProgramOptionsException("no input source specified"); } + fInputUri = vm["input"].as<std::vector<std::string>>(); + + if (vm.count("config") == 0u) { throw ProgramOptionsException("no configuration file specified"); } + + L_(debug) << "input sources (" << fInputUri.size() << "):"; + for (auto& inputUri : fInputUri) { + L_(debug) << " " << inputUri; + } +} diff --git a/reco/app/cbmreco_fairrun/ProgramOptions.h b/reco/app/cbmreco_fairrun/ProgramOptions.h new file mode 100644 index 0000000000000000000000000000000000000000..d57563303cee74f8a10709ded325d5801baf655b --- /dev/null +++ b/reco/app/cbmreco_fairrun/ProgramOptions.h @@ -0,0 +1,65 @@ +/* Copyright (C) 2022 Johann Wolfgang Goethe-Universitaet Frankfurt, Frankfurt am Main + SPDX-License-Identifier: GPL-3.0-only + Authors: Jan de Cuveland [committer] */ + +#ifndef APP_CBMRECO_PROGRAMOPTIONS_H +#define APP_CBMRECO_PROGRAMOPTIONS_H + +#include <cstdint> +#include <stdexcept> +#include <string> +#include <vector> + +/** @class ProgramOptionsException + ** @brief Program options exception class + ** @author Jan de Cuveland <cuveland@compeng.uni-frankfurt.de> + ** @since 16 March 2022 + **/ +class ProgramOptionsException : public std::runtime_error { +public: + explicit ProgramOptionsException(const std::string& what_arg = "") : std::runtime_error(what_arg) {} +}; + +/** @class ProgramOptions + ** @brief Program options class for the "cbmreco_fairrun" application + ** @author Jan de Cuveland <cuveland@compeng.uni-frankfurt.de> + ** @since 16 March 2022 + **/ +class ProgramOptions { +public: + /** @brief Standard constructor with command line arguments **/ + ProgramOptions(int argc, char* argv[]) { ParseOptions(argc, argv); } + + /** @brief Copy constructor forbidden **/ + ProgramOptions(const ProgramOptions&) = delete; + + /** @brief Assignment operator forbidden **/ + void operator=(const ProgramOptions&) = delete; + + /** @brief Get URI specifying input timeslice stream source(s) **/ + [[nodiscard]] const std::vector<std::string>& InputUri() const { return fInputUri; } + + /** @brief Get output file name (.root format) **/ + [[nodiscard]] const std::string& OutputRootFile() const { return fOutputRootFile; } + + /** @brief Get configuration file name (.yaml format) **/ + [[nodiscard]] const std::string& ConfigYamlFile() const { return fConfigYamlFile; } + + /** @brief Get save configuration file name (.yaml format) **/ + [[nodiscard]] const std::string& SaveConfigYamlFile() const { return fSaveConfigYamlFile; } + + /** @brief Get maximum number of timeslices to process **/ + [[nodiscard]] int32_t MaxNumTs() const { return fMaxNumTs; } + +private: + /** @brief Parse command line arguments using boost program_options **/ + void ParseOptions(int argc, char* argv[]); + + std::vector<std::string> fInputUri; ///< URI(s) specifying input timeslice stream source(s) + std::string fOutputRootFile = "/dev/null"; ///< Output file name (.root format) + std::string fConfigYamlFile; ///< Configuration file name (.yaml format) + std::string fSaveConfigYamlFile; ///< Save configuration file name (.yaml format) + int32_t fMaxNumTs = INT32_MAX; ///< Maximum number of timeslices to process +}; + +#endif /* APP_CBMRECO_PROGRAMOPTIONS_H */ diff --git a/reco/app/cbmreco_fairrun/main.cxx b/reco/app/cbmreco_fairrun/main.cxx new file mode 100644 index 0000000000000000000000000000000000000000..461e8b6d1c1e40f4aef153c1229a8a67210e62f4 --- /dev/null +++ b/reco/app/cbmreco_fairrun/main.cxx @@ -0,0 +1,23 @@ +/* Copyright (C) 2022 Johann Wolfgang Goethe-Universitaet Frankfurt, Frankfurt am Main + SPDX-License-Identifier: GPL-3.0-only + Authors: Jan de Cuveland [committer] */ + +#include "Application.h" +#include "ProgramOptions.h" +#include "log.hpp" + +int main(int argc, char* argv[]) +{ + try { + ProgramOptions opt(argc, argv); + Application app(opt); + app.Run(); + } + catch (std::exception const& e) { + L_(fatal) << e.what(); + return EXIT_FAILURE; + } + + L_(info) << "exiting"; + return EXIT_SUCCESS; +} diff --git a/reco/tasks/CMakeLists.txt b/reco/tasks/CMakeLists.txt index 7d98de2467e7d82c137bfb90a86f20ceb3bf3e22..0bca7c915bf925af763a7d375b1be84c2e365d60 100644 --- a/reco/tasks/CMakeLists.txt +++ b/reco/tasks/CMakeLists.txt @@ -68,6 +68,7 @@ Base CbmBase CbmData Algo +external::yaml-cpp ) # --------------------------------------------------------- diff --git a/reco/tasks/CbmReco.cxx b/reco/tasks/CbmReco.cxx index 373191c2b9e1233e406d4ddb8c95e7667775be81..ec47da729f594aa3a1e9759b64954cde52bbd7a6 100644 --- a/reco/tasks/CbmReco.cxx +++ b/reco/tasks/CbmReco.cxx @@ -22,12 +22,68 @@ #include <memory> #include <string> +#include <yaml-cpp/yaml.h> + using std::cout; using std::endl; using std::make_unique; using std::string; +// ----- Load configuration from YAML file -------------------------------- +void CbmRecoConfig::LoadYaml(const std::string& filename) +{ + YAML::Node config = YAML::LoadFile(filename); + // --- Digi trigger + fTriggerDet = ToCbmModuleIdCaseInsensitive(config["trigger"]["detector"].as<std::string>()); + fTriggerWin = config["trigger"]["window"].as<double>(); + fTriggerThreshold = config["trigger"]["threshold"].as<size_t>(); + fTriggerDeadTime = config["trigger"]["deadtime"].as<double>(); + // --- Event builder: (detector -> (tMin, tMax)) + if (auto eventbuilder = config["eventbuilder"]) { + if (auto windows = eventbuilder["windows"]) { + for (YAML::const_iterator it = windows.begin(); it != windows.end(); ++it) { + auto det = ToCbmModuleIdCaseInsensitive(it->first.as<std::string>()); + auto lower = it->second[0].as<double>(); + auto upper = it->second[1].as<double>(); + fEvtbuildWindows[det] = std::make_pair(lower, upper); + } + } + } + // --- Branch persistence in output file + fStoreTimeslice = config["store"]["timeslice"].as<bool>(); + fStoreTrigger = config["store"]["triggers"].as<bool>(); + fStoreEvents = config["store"]["events"].as<bool>(); +} +// ---------------------------------------------------------------------------- + + +// ----- Save configuration to YAML file ---------------------------------- +void CbmRecoConfig::SaveYaml(const std::string& filename) +{ + YAML::Node config; + // --- Digi trigger + config["trigger"]["detector"] = ToString(fTriggerDet); + config["trigger"]["window"] = fTriggerWin; + config["trigger"]["threshold"] = fTriggerThreshold; + config["trigger"]["deadtime"] = fTriggerDeadTime; + // --- Event builder: (detector -> (tMin, tMax)) + for (const auto& [key, value] : fEvtbuildWindows) { + auto det = ToString(key); + config["eventbuilder"]["windows"][det].push_back(value.first); + config["eventbuilder"]["windows"][det].push_back(value.second); + }; + // --- Branch persistence in output file + config["store"]["timeslice"] = fStoreTimeslice; + config["store"]["triggers"] = fStoreTrigger; + config["store"]["events"] = fStoreEvents; + // --- + std::ofstream fout(filename); + fout << config; +} +// ---------------------------------------------------------------------------- + + // ----- Constructor from single source ----------------------------------- CbmReco::CbmReco(string source, TString outFile, int32_t numTs, const CbmRecoConfig& config, uint16_t port) : fSourceNames {source} diff --git a/reco/tasks/CbmReco.h b/reco/tasks/CbmReco.h index 28aaf291efaecd9cab7d6fef7b65fbebbe922d9e..61992fc1a7da684d188250f9d56decad538d979c 100644 --- a/reco/tasks/CbmReco.h +++ b/reco/tasks/CbmReco.h @@ -32,6 +32,9 @@ public: bool fStoreTimeslice = false; bool fStoreTrigger = false; bool fStoreEvents = false; + // --- Load/save using yaml-cpp + void LoadYaml(const std::string& filename); + void SaveYaml(const std::string& filename); // --- Destructor virtual ~CbmRecoConfig() {}; diff --git a/reco/tasks/CbmRecoConfigExample.yaml b/reco/tasks/CbmRecoConfigExample.yaml new file mode 100644 index 0000000000000000000000000000000000000000..69bd607e3bd7811a52a98b0cad9abd4bae93e6c6 --- /dev/null +++ b/reco/tasks/CbmRecoConfigExample.yaml @@ -0,0 +1,12 @@ +trigger: + detector: STS + window: 10 + threshold: 100 + deadtime: 50 +eventbuilder: + windows: + STS: [-20, 30] +store: + timeslice: false + triggers: false + events: true