diff --git a/Projects/AnalysisBase/package_filters.txt b/Projects/AnalysisBase/package_filters.txt index d9631a2366a70fb67da56a99174899ae0ee073cf..aa36f316318aae75c688c2722718b2b966ece4c7 100644 --- a/Projects/AnalysisBase/package_filters.txt +++ b/Projects/AnalysisBase/package_filters.txt @@ -105,6 +105,7 @@ + Reconstruction/egamma/egammaUtils + Reconstruction/tauRecTools + Reconstruction/PanTau/PanTauAlgs ++ Reconstruction/LwtnnUtils + Tools/ART + Tools/DirectIOART + Tools/PathResolver diff --git a/Reconstruction/LwtnnUtils/CMakeLists.txt b/Reconstruction/LwtnnUtils/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..a10e47c1630b2f0d1d22cde527f3896610ed0d0d --- /dev/null +++ b/Reconstruction/LwtnnUtils/CMakeLists.txt @@ -0,0 +1,19 @@ +# Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration +# + +# Declare the package name: +atlas_subdir( LwtnnUtils ) + +# External dependencies: +find_package( lwtnn ) +find_package( Eigen ) + +# Build a shared library: +atlas_add_library( LwtnnUtils + LwtnnUtils/*.h # <-- for Xcode, not needed otherwise + src/FastGraph.cxx + src/FastInputPreprocessor.cxx + PUBLIC_HEADERS LwtnnUtils + INCLUDE_DIRS ${LWTNN_INCLUDE_DIRS} ${EIGEN_INCLUDE_DIRS} + LINK_LIBRARIES ${LWTNN_LIBRARIES} ${EIGEN_LIBRARIES} ) + diff --git a/Reconstruction/LwtnnUtils/LwtnnUtils/FastGraph.h b/Reconstruction/LwtnnUtils/LwtnnUtils/FastGraph.h new file mode 100644 index 0000000000000000000000000000000000000000..89e26d44d7fe31a2bda6c99504904ce35944874d --- /dev/null +++ b/Reconstruction/LwtnnUtils/LwtnnUtils/FastGraph.h @@ -0,0 +1,72 @@ +// this is -*- C++ -*- +/* + Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration +*/ + +// Any modifications to this file may be copied to lwtnn[1] without +// attribution. +// +// [1]: https::www.github.com/lwtnn/lwtnn + +#ifndef LWTNN_UTILS_FAST_GRAPH_H +#define LWTNN_UTILS_FAST_GRAPH_H + +#include "lwtnn/lightweight_network_config.hh" + +#include <Eigen/Dense> + +namespace lwt { + class Graph; +} + +namespace lwt::atlas { + + class FastInputPreprocessor; + class FastInputVectorPreprocessor; + class InputOrder; + + struct SourceIndices + { + std::vector<size_t> scalar; + std::vector<size_t> sequence; + }; + + // Graph class + class FastGraph + { + public: + // Since a graph has multiple input nodes, we actually call + typedef std::vector<Eigen::VectorXd> NodeVec; + typedef std::vector<Eigen::MatrixXd> SeqNodeVec; + + + // In cases where the graph has multiple outputs, we have to + // define a "default" output, so that calling "compute" with no + // output specified doesn't lead to ambiguity. + FastGraph(const GraphConfig& config, const InputOrder& order, + std::string default_output = ""); + + ~FastGraph(); + FastGraph(FastGraph&) = delete; + FastGraph& operator=(FastGraph&) = delete; + + // The simpler "compute" function + Eigen::VectorXd compute(const NodeVec&, const SeqNodeVec& = {}) const; + + private: + typedef FastInputPreprocessor IP; + typedef FastInputVectorPreprocessor IVP; + typedef std::vector<IP*> Preprocs; + typedef std::vector<IVP*> VecPreprocs; + + Eigen::VectorXd compute(const NodeVec&, const SeqNodeVec&, size_t) const; + Graph* m_graph; + Preprocs m_preprocs; + VecPreprocs m_vec_preprocs; + size_t m_default_output; + // the mapping from a node in the network to a user input node + SourceIndices m_input_indices; + }; +} + +#endif diff --git a/Reconstruction/LwtnnUtils/LwtnnUtils/FastInputPreprocessor.h b/Reconstruction/LwtnnUtils/LwtnnUtils/FastInputPreprocessor.h new file mode 100644 index 0000000000000000000000000000000000000000..b04688760d61a3276a67762fbbdc7e96e8494007 --- /dev/null +++ b/Reconstruction/LwtnnUtils/LwtnnUtils/FastInputPreprocessor.h @@ -0,0 +1,57 @@ +// this is -*- C++ -*- +/* + Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration +*/ + +// Any modifications to this file may be copied to lwtnn[1] without +// attribution. +// +// [1]: https::www.github.com/lwtnn/lwtnn + + +#ifndef LWTNN_UTILS_FAST_INPUT_PREPROCESSOR_H +#define LWTNN_UTILS_FAST_INPUT_PREPROCESSOR_H + +#include "lwtnn/lightweight_network_config.hh" +#include "lwtnn/Exceptions.hh" + +#include <Eigen/Dense> +#include <vector> + +namespace lwt::atlas { + + using Eigen::VectorXd; + using Eigen::MatrixXd; + + // ______________________________________________________________________ + // input preprocessor (handles normalization and packing into Eigen) + + class FastInputPreprocessor + { + public: + FastInputPreprocessor(const std::vector<Input>& inputs, + const std::vector<std::string>& order); + VectorXd operator()(const VectorXd&) const; + private: + // input transformations + VectorXd m_offsets; + VectorXd m_scales; + std::vector<size_t> m_indices; + }; + + class FastInputVectorPreprocessor + { + public: + FastInputVectorPreprocessor(const std::vector<Input>& inputs, + const std::vector<std::string>& order); + MatrixXd operator()(const MatrixXd&) const; + private: + // input transformations + VectorXd m_offsets; + VectorXd m_scales; + std::vector<size_t> m_indices; + }; +} + + +#endif diff --git a/Reconstruction/LwtnnUtils/LwtnnUtils/InputOrder.h b/Reconstruction/LwtnnUtils/LwtnnUtils/InputOrder.h new file mode 100644 index 0000000000000000000000000000000000000000..9df4677d991c4bad004aa173bb57c01c357acf40 --- /dev/null +++ b/Reconstruction/LwtnnUtils/LwtnnUtils/InputOrder.h @@ -0,0 +1,36 @@ +// this is -*- C++ -*- +/* + Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration +*/ + +// Any modifications to this file may be copied to lwtnn[1] without +// attribution. +// +// [1]: https::www.github.com/lwtnn/lwtnn + +#ifndef LWTNN_UTILS_INPUT_ORDER_H +#define LWTNN_UTILS_INPUT_ORDER_H + +#include <vector> +#include <string> + +namespace lwt::atlas { + + // the user should specify what inputs they are going to feed to + // the network. This is different from the ordering that the + // network uses internally: some variables that are passed in + // might be dropped or reorganized. + typedef std::vector< + std::pair<std::string, std::vector<std::string>> + > order_t; + + struct InputOrder + { + order_t scalar; + order_t sequence; + }; + +} + + +#endif diff --git a/Reconstruction/LwtnnUtils/README.md b/Reconstruction/LwtnnUtils/README.md new file mode 100644 index 0000000000000000000000000000000000000000..b585f9569edd5705e9dc494073e73cb13fb6bb06 --- /dev/null +++ b/Reconstruction/LwtnnUtils/README.md @@ -0,0 +1,17 @@ +lwtnn Utilities +=============== + +This is some Atlas-side helpers for the [lwtnn][1] package. + +At some point things here might be migrated upstream, but we keep this +package around for development that might not want to wait for +`AtlasExternals` to be updated. + +Package Contents +---------------- + +- `FastGraph`: Faster version of `lwt::LightweightGraph`. Takes + vectors rather than `std::map<std::string,...>` inputs. + + +[1]: https://www.github.com/lwtnn/lwtnn diff --git a/Reconstruction/LwtnnUtils/src/FastGraph.cxx b/Reconstruction/LwtnnUtils/src/FastGraph.cxx new file mode 100644 index 0000000000000000000000000000000000000000..b1cdef0b6112ad0c2b7fc99871a291b133452ecf --- /dev/null +++ b/Reconstruction/LwtnnUtils/src/FastGraph.cxx @@ -0,0 +1,170 @@ +// this is -*- C++ -*- +/* + Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration +*/ + +// Any modifications to this file may be copied to lwtnn[1] without +// attribution. +// +// [1]: https::www.github.com/lwtnn/lwtnn + +#include "LwtnnUtils/FastGraph.h" +#include "LwtnnUtils/FastInputPreprocessor.h" +#include "LwtnnUtils/InputOrder.h" +#include "lwtnn/Graph.hh" +#include <Eigen/Dense> + +namespace { + using namespace Eigen; + using namespace lwt; + using namespace lwt::atlas; + + typedef atlas::FastGraph::NodeVec NodeVec; + typedef atlas::FastInputPreprocessor IP; + typedef std::vector<IP*> Preprocs; + typedef atlas::FastGraph::SeqNodeVec SeqNodeVec; + typedef atlas::FastInputVectorPreprocessor IVP; + typedef std::vector<IVP*> VecPreprocs; + + + // this is used internally to ensure that we only look up map inputs + // when the network asks for them. + class LazySource: public ISource + { + public: + LazySource(const NodeVec&, const SeqNodeVec&, + const Preprocs&, const VecPreprocs&, + const SourceIndices& input_indices); + virtual VectorXd at(size_t index) const override; + virtual MatrixXd matrix_at(size_t index) const override; + private: + const NodeVec& m_nodes; + const SeqNodeVec& m_seqs; + const Preprocs& m_preprocs; + const VecPreprocs& m_vec_preprocs; + const SourceIndices& m_input_indices; + }; + + LazySource::LazySource(const NodeVec& n, const SeqNodeVec& s, + const Preprocs& p, const VecPreprocs& v, + const SourceIndices& i): + m_nodes(n), m_seqs(s), m_preprocs(p), m_vec_preprocs(v), + m_input_indices(i) + { + } + VectorXd LazySource::at(size_t index) const + { + const auto& preproc = *m_preprocs.at(index); + size_t source_index = m_input_indices.scalar.at(index); + if (source_index >= m_nodes.size()) { + throw NNEvaluationException( + "The NN needs an input VectorXd at position " + + std::to_string(source_index) + " but only " + + std::to_string(m_nodes.size()) + " inputs were given"); + } + return preproc(m_nodes.at(source_index)); + } + MatrixXd LazySource::matrix_at(size_t index) const + { + const auto& preproc = *m_vec_preprocs.at(index); + size_t source_index = m_input_indices.sequence.at(index); + if (source_index >= m_nodes.size()) { + throw NNEvaluationException( + "The NN needs an input MatrixXd at position " + + std::to_string(source_index) + " but only " + + std::to_string(m_nodes.size()) + " inputs were given"); + } + return preproc(m_seqs.at(source_index)); + } + + // utility functions + // + // Build a mapping from the inputs in the saved network to the + // inputs that the user is going to hand us. + std::vector<size_t> get_node_indices( + const order_t& order, + const std::vector<lwt::InputNodeConfig>& inputs) + { + std::map<std::string, size_t> order_indices; + for (size_t i = 0; i < order.size(); i++) { + order_indices[order.at(i).first] = i; + } + std::vector<size_t> node_indices; + for (const lwt::InputNodeConfig& input: inputs) { + if (!order_indices.count(input.name)) { + throw NNConfigurationException("Missing input " + input.name); + } + node_indices.push_back(order_indices.at(input.name)); + } + return node_indices; + } + + +} +namespace lwt::atlas { + // ______________________________________________________________________ + // Fast Graph + + typedef FastGraph::NodeVec NodeVec; + FastGraph::FastGraph(const GraphConfig& config, const InputOrder& order, + std::string default_output): + m_graph(new Graph(config.nodes, config.layers)) + { + + m_input_indices.scalar = get_node_indices( + order.scalar, config.inputs); + + m_input_indices.sequence = get_node_indices( + order.sequence, config.input_sequences); + + for (size_t i = 0; i < config.inputs.size(); i++) { + const lwt::InputNodeConfig& node = config.inputs.at(i); + size_t input_node = m_input_indices.scalar.at(i); + std::vector<std::string> varorder = order.scalar.at(input_node).second; + m_preprocs.emplace_back( + new FastInputPreprocessor(node.variables, varorder)); + } + for (size_t i = 0; i < config.input_sequences.size(); i++) { + const lwt::InputNodeConfig& node = config.input_sequences.at(i); + size_t input_node = m_input_indices.sequence.at(i); + std::vector<std::string> varorder = order.sequence.at(input_node).second; + m_vec_preprocs.emplace_back( + new FastInputVectorPreprocessor(node.variables, varorder)); + } + if (default_output.size() > 0) { + if (!config.outputs.count(default_output)) { + throw NNConfigurationException("no output node" + default_output); + } + m_default_output = config.outputs.at(default_output).node_index; + } else if (config.outputs.size() == 1) { + m_default_output = config.outputs.begin()->second.node_index; + } else { + throw NNConfigurationException("you must specify a default output"); + } + } + + FastGraph::~FastGraph() { + delete m_graph; + for (auto& preproc: m_preprocs) { + delete preproc; + preproc = 0; + } + for (auto& preproc: m_vec_preprocs) { + delete preproc; + preproc = 0; + } + } + + VectorXd FastGraph::compute(const NodeVec& nodes, + const SeqNodeVec& seq) const { + return compute(nodes, seq, m_default_output); + } + VectorXd FastGraph::compute(const NodeVec& nodes, + const SeqNodeVec& seq, + size_t idx) const { + LazySource source(nodes, seq, m_preprocs, m_vec_preprocs, + m_input_indices); + return m_graph->compute(source, idx); + } + +} diff --git a/Reconstruction/LwtnnUtils/src/FastInputPreprocessor.cxx b/Reconstruction/LwtnnUtils/src/FastInputPreprocessor.cxx new file mode 100644 index 0000000000000000000000000000000000000000..01da6604e5e7d3646819eb909b19fceb8e4b724f --- /dev/null +++ b/Reconstruction/LwtnnUtils/src/FastInputPreprocessor.cxx @@ -0,0 +1,114 @@ +// this is -*- C++ -*- +/* + Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration +*/ + +// Any modifications to this file may be copied to lwtnn[1] without +// attribution. +// +// [1]: https::www.github.com/lwtnn/lwtnn + +#include "LwtnnUtils/FastInputPreprocessor.h" +#include "lwtnn/Exceptions.hh" + +namespace { + // utility functions + // + // Build a mapping from the inputs in the saved network to the + // inputs that the user is going to hand us. + std::vector<size_t> get_value_indices( + const std::vector<std::string>& order, + const std::vector<lwt::Input>& inputs) + { + std::map<std::string, size_t> order_indices; + for (size_t i = 0; i < order.size(); i++) { + order_indices[order.at(i)] = i; + } + std::vector<size_t> value_indices; + for (const lwt::Input& input: inputs) { + if (!order_indices.count(input.name)) { + throw lwt::NNConfigurationException("Missing input " + input.name); + } + value_indices.push_back(order_indices.at(input.name)); + } + return value_indices; + } + +} + +namespace lwt::atlas { + // ______________________________________________________________________ + // FastInput preprocessors + + // simple feed-forwared version + FastInputPreprocessor::FastInputPreprocessor( + const std::vector<Input>& inputs, + const std::vector<std::string>& order): + m_offsets(inputs.size()), + m_scales(inputs.size()) + { + size_t in_num = 0; + for (const auto& input: inputs) { + m_offsets(in_num) = input.offset; + m_scales(in_num) = input.scale; + in_num++; + } + m_indices = get_value_indices(order, inputs); + } + VectorXd FastInputPreprocessor::operator()(const VectorXd& in) const { + VectorXd invec(m_indices.size()); + size_t input_number = 0; + for (size_t index: m_indices) { + if (static_cast<int>(index) >= in.rows()) { + throw NNEvaluationException( + "index " + std::to_string(index) + " is out of range, scalar " + "input only has " + std::to_string(in.rows()) + " entries"); + } + invec(input_number) = in(index); + input_number++; + } + return (invec + m_offsets).cwiseProduct(m_scales); + } + + + // Input vector preprocessor + FastInputVectorPreprocessor::FastInputVectorPreprocessor( + const std::vector<Input>& inputs, + const std::vector<std::string>& order): + m_offsets(inputs.size()), + m_scales(inputs.size()) + { + size_t in_num = 0; + for (const auto& input: inputs) { + m_offsets(in_num) = input.offset; + m_scales(in_num) = input.scale; + in_num++; + } + // require at least one input at configuration, since we require + // at least one for evaluation + if (in_num == 0) { + throw NNConfigurationException("need at least one input"); + } + m_indices = get_value_indices(order, inputs); + } + MatrixXd FastInputVectorPreprocessor::operator()(const MatrixXd& in) const { + using namespace Eigen; + size_t n_cols = in.cols(); + MatrixXd inmat(m_indices.size(), n_cols); + size_t in_num = 0; + for (size_t index: m_indices) { + if (static_cast<int>(index) >= in.rows()) { + throw NNEvaluationException( + "index " + std::to_string(index) + " is out of range, sequence " + "input only has " + std::to_string(in.rows()) + " entries"); + } + inmat.row(in_num) = in.row(index); + in_num++; + } + if (n_cols == 0) { + return MatrixXd(m_indices.size(), 0); + } + return m_scales.asDiagonal() * (inmat.colwise() + m_offsets); + } + +}