#pragma once /** * @defgroup ext Extensions * @defgroup api API * @defgroup abc ABCs */ #include #include #include #include #include #include #include #include namespace queryosity { namespace ensemble { template class slotted { public: slotted() = default; virtual ~slotted() = default; public: virtual std::vector const &get_slots() const = 0; T *get_slot(unsigned int i) const; operator std::vector const &() const { return this->get_slots(); } unsigned int size() const; }; template unsigned int check(std::vector const &first, std::vector const &...args); template auto invoke(Fn const &fn, std::vector const &...args) -> std::enable_if_t< !std::is_void_v>, std::vector>>; template auto invoke(Fn const &fn, std::vector const &...args) -> std::enable_if_t< std::is_void_v>, void>; } // namespace ensemble namespace multithread { class core { public: core(int suggestion); core(const core &) = default; core &operator=(const core &) = default; virtual ~core() = default; /** * @brief Run the function on the underlying slots, multi-threading if * enabled. * @param[in] fn Function to be called. * @param[in] args (Optional) arguments applied per-slot to function. * @details The methods are called on each slot, and the model is left * untouched. */ template void run(Fn const &fn, std::vector const &...args) const; bool is_enabled() const { return m_enabled; } unsigned int concurrency() const { return m_concurrency; } protected: bool m_enabled; unsigned int m_concurrency; }; } // namespace multithread } // namespace queryosity inline queryosity::multithread::core::core(int suggestion) : m_enabled(suggestion) { if (!suggestion) // single-threaded m_concurrency = 1; else if (suggestion < 0) // maximum thread count m_concurrency = std::thread::hardware_concurrency(); else // (up to maximum) requested thread count m_concurrency = std::min(std::thread::hardware_concurrency(), suggestion); } template void queryosity::multithread::core::run( Fn const &fn, std::vector const &...args) const { auto nslots = ensemble::check(args...); if (this->is_enabled()) { // enabled std::vector pool; pool.reserve(nslots); for (size_t islot = 0; islot < nslots; ++islot) { pool.emplace_back(fn, args.at(islot)...); } for (auto &&thread : pool) { thread.join(); } } else { // disabled for (size_t islot = 0; islot < nslots; ++islot) { fn(args.at(islot)...); } } } template inline unsigned int queryosity::ensemble::check(std::vector const &first, std::vector const &...args) { assert(((first.size() == args.size()) && ...)); (args.size(), ...); // suppress GCC unused parameter warnings return first.size(); } template inline auto queryosity::ensemble::invoke(Fn const &fn, std::vector const &...args) -> std::enable_if_t< !std::is_void_v>, std::vector>> { auto nslots = check(args...); using slot_t = typename std::invoke_result_t; typename std::vector invoked; invoked.reserve(nslots); for (size_t i = 0; i < nslots; ++i) { invoked.push_back(std::move((fn(args.at(i)...)))); } return invoked; } template inline auto queryosity::ensemble::invoke(Fn const &fn, std::vector const &...args) -> std::enable_if_t< std::is_void_v>, void> { auto nslots = check(args...); for (size_t i = 0; i < nslots; ++i) { fn(args.at(i)...); } } template T *queryosity::ensemble::slotted::get_slot(unsigned int islot) const { return this->get_slots().at(islot); } template unsigned int queryosity::ensemble::slotted::size() const { return this->get_slots().size(); } #include #include #include namespace queryosity { template class lazy; namespace systematic { template auto get_variation_names(Nodes const &...nodes) -> std::set; template class resolver; } // namespace systematic } // namespace queryosity template auto queryosity::systematic::get_variation_names(Nodes const &...nodes) -> std::set { std::set variation_names; (variation_names.merge(nodes.get_variation_names()), ...); return variation_names; } namespace queryosity { class action { public: action() = default; virtual ~action() = default; virtual void initialize(unsigned int slot, unsigned long long begin, unsigned long long end) = 0; virtual void execute(unsigned int slot, unsigned long long entry) = 0; virtual void finalize(unsigned int slot) = 0; protected: }; } // namespace queryosity #include #include #include namespace queryosity { /** * @brief Compute quantities of interest. */ namespace column { class node : public action { public: node() = default; virtual ~node() = default; }; //--------------------------------------------------- // view can actually report on the concrete data type //--------------------------------------------------- template class view { public: using value_type = T; public: template class converted_from; template class interface_of; public: view() = default; virtual ~view() = default; virtual T const &value() const = 0; virtual T const *field() const; }; //------------------------------------ // conversion between compatible types //------------------------------------ template template class view::converted_from : public view { public: converted_from(view const &from); virtual ~converted_from() = default; public: virtual const To &value() const override; private: view const *m_from; mutable To m_converted_from; }; //------------------------------------------ // interface between inherited -> base type //------------------------------------------ template template class view::interface_of : public view { public: interface_of(view const &from); virtual ~interface_of() = default; public: virtual const To &value() const override; private: view const *m_impl; }; template class valued : public column::node, public view { public: using value_type = typename view::value_type; public: valued() = default; virtual ~valued() = default; virtual void initialize(unsigned int slot, unsigned long long begin, unsigned long long end) override; virtual void execute(unsigned int slot, unsigned long long entry) override; virtual void finalize(unsigned int slot) override; }; // costly to move around template class variable { public: variable() = default; template variable(view const &val); ~variable() = default; variable(variable &&) = default; variable &operator=(variable &&) = default; T const &value() const; T const *field() const; protected: std::unique_ptr> m_view; }; /** * @brief Column observable. * @tparam Val Column data type. */ template class observable { public: /** * @brief Constructor out of a variable. */ observable(variable const &obs); ~observable() = default; /** * @brief Compute and retrieve the value of the column. * @return Value of the column. * @brief The column is *not* computed until the method is called at least * once during the dataflow entry processing. Once computed, it is cached for * future retrievals. */ Val const &value() const; Val const *field() const; /** * @brief Shortcut for `value()`. */ Val const &operator*() const; /** * @brief Indirection semantic for non-trivial data types. */ Val const *operator->() const; protected: const variable &m_var; }; template std::unique_ptr> view_as(view const &from); class computation; template class reader; template class fixed; template class calculation; template class definition; template class conversion; template class equation; template class composition; template class evaluator; template struct constant; template struct expression; template struct series; template struct nominal; template struct variation; template constexpr std::true_type check_reader(typename column::reader const &); constexpr std::false_type check_reader(...); template constexpr std::true_type check_fixed(typename column::fixed const &); constexpr std::false_type check_fixed(...); template constexpr std::true_type check_definition(typename column::definition const &); constexpr std::false_type check_definition(...); template constexpr std::true_type check_equation(typename column::equation const &); constexpr std::false_type check_equation(...); template constexpr std::true_type check_composition(typename column::composition const &); constexpr std::false_type check_composition(...); template constexpr bool is_reader_v = decltype(check_reader(std::declval const &>()))::value; template constexpr bool is_fixed_v = decltype(check_fixed(std::declval const &>()))::value; template constexpr bool is_definition_v = decltype(check_definition(std::declval const &>()))::value; template constexpr bool is_equation_v = decltype(check_equation(std::declval const &>()))::value; template constexpr bool is_composition_v = decltype(check_composition(std::declval const &>()))::value; template struct is_evaluator : std::false_type {}; template struct is_evaluator> : std::true_type {}; template constexpr bool is_evaluatable_v = is_evaluator::value; template struct deduce_equation; template struct deduce_equation> { using type = column::equation(std::decay_t...)>; }; template using equation_t = typename deduce_equation< typename column::expression::function_type>::type; template using evaluated_t = typename T::evaluated_type; template using value_t = std::decay_t().value())>; } // namespace column template constexpr bool is_column_v = std::is_base_of_v; } // namespace queryosity template void queryosity::column::valued::initialize(unsigned int, unsigned long long, unsigned long long) {} template void queryosity::column::valued::execute(unsigned int, unsigned long long) {} template void queryosity::column::valued::finalize(unsigned int) {} template T const *queryosity::column::view::field() const { return &this->value(); } template template queryosity::column::view::converted_from::converted_from( view const &from) : m_from(&from) {} template template const To &queryosity::column::view::converted_from::value() const { m_converted_from = std::move(m_from->value()); return m_converted_from; } template template queryosity::column::view::interface_of::interface_of( view const &from) : m_impl(&from) {} template template const Base &queryosity::column::view::interface_of::value() const { return m_impl->value(); } template std::unique_ptr> queryosity::column::view_as(view const &from) { static_assert(std::is_same_v || std::is_base_of_v || std::is_convertible_v, "incompatible value types"); if constexpr (std::is_same_v || std::is_base_of_v) { return std::make_unique< typename queryosity::column::view::template interface_of>( from); } else if constexpr (std::is_convertible_v) { return std::make_unique< typename queryosity::column::view::template converted_from>( from); } } // -------- // variable // -------- template template queryosity::column::variable::variable(view const &val) : m_view(view_as(val)) {} template T const &queryosity::column::variable::value() const { return m_view->value(); } template T const *queryosity::column::variable::field() const { return m_view->field(); } template queryosity::column::observable::observable(const variable &var) : m_var(var) {} template T const &queryosity::column::observable::operator*() const { return m_var.value(); } template T const *queryosity::column::observable::operator->() const { return m_var.field(); } template T const &queryosity::column::observable::value() const { return m_var.value(); } template T const *queryosity::column::observable::field() const { return m_var.field(); } namespace queryosity { namespace dataset { /** * @ingroup abc * @brief Custom dataset source */ class source : public action { public: source() = default; virtual ~source() = default; /** * @brief Inform the dataset of parallelism. */ virtual void parallelize(unsigned int concurrency) = 0; /** * @brief Initialize dataset processing */ virtual void initialize(); /** * @brief Determine dataset partition for parallel processing. * @return Dataset partition * * @details * A non-empty partition **MUST** begin from the `0` and be sorted contiguous * order, e.g.: * @code{.cpp} {{0,100},{100,200}, ..., {900,1000}} @endcode * If a dataset returns an empty partition, it relinquishes its control over * the entry loop to another dataset with a non-empty partition. * @attention * - Non-empty partitions reported from multiple datasets need to be aligned * to form a common denominator partition over which the dataset processing is * parallelized. As such, they **MUST** have (1) at minimum, the same total * number of entries, and (2) ideally, shared sub-range boundaries. * - Any dataset reporting an empty partition **MUST** be able to fulfill * `dataset::source::execute()` calls for any entry number as requested by the * other datasets loaded in the dataflow. * */ virtual std::vector> partition() = 0; /** * @brief Enter an entry loop. * @param[in] slot Thread slot number. * @param[in] begin First entry number processed. * @param[in] end Loop stops after `end-1`-th entry has been processed. */ virtual void initialize(unsigned int slot, unsigned long long begin, unsigned long long end) override; /** * @brief Process an entry. * @param[in] slot Thread slot number. * @param[in] entry Entry being processed. */ virtual void execute(unsigned int slot, unsigned long long entry) override; /** * @brief Exit an entry loop. * @param[in] slot Thread slot number. * @param[in] entry Entry being processed. */ virtual void finalize(unsigned int slot) override; /** * @brief Finalize processing the dataset. */ virtual void finalize(); }; /** * @ingroup abc * @brief Custom dataset reader */ template class reader : public source { public: virtual ~reader() = default; template std::unique_ptr> read_column(unsigned int slot, const std::string &name); /** * @brief Read a column. * @tparam Val Column value type. * @param slot Thread slot number. * @param name Column name. * @return Column implementation. */ template std::unique_ptr> read(unsigned int slot, const std::string &name); }; } // namespace dataset } // namespace queryosity template using read_column_t = typename decltype(std::declval().template read_column( std::declval(), std::declval()))::element_type; namespace queryosity { namespace column { struct range; /** * @ingroup abc * @brief Read columns from a dataset. * @tparam T column data type. */ template class reader : public valued { public: using const_reference = T const &; public: reader(); virtual ~reader() = default; /** * Read the value of the column at current entry. * @param[in] slot Multithreaded slot enumerator. * @param[in] entry Dataset global entry enumerator. * @return Column value at current entry. */ virtual const_reference read(unsigned int, unsigned long long) const = 0; virtual const_reference value() const override; virtual void execute(unsigned int, unsigned long long) final override; protected: mutable T const *m_addr; mutable bool m_updated; private: unsigned int m_slot; unsigned long long m_entry; }; } // namespace column } // namespace queryosity #include #include #include #include #include #include #include #include namespace queryosity { /** * @brief Process a dataset. */ namespace dataset { using part_t = std::pair; class source; class player; class processor; template class reader; template struct input; template class loaded; template class column; template class columns; struct head { head(long long nrows) : nrows(nrows) {} long long nrows; operator long long() { return nrows; } }; struct offset { offset(unsigned long long pos) : pos(pos) {} unsigned long long pos; operator unsigned long long() { return pos; } }; struct weight { weight(double value) : value(value) {} double value; operator double() { return value; } }; } // namespace dataset } // namespace queryosity template queryosity::column::reader::reader() : m_addr(nullptr), m_updated(false), m_slot(0), m_entry(0) {} template T const &queryosity::column::reader::value() const { if (!this->m_updated) { m_addr = &(this->read(this->m_slot, this->m_entry)); m_updated = true; } return *m_addr; } template void queryosity::column::reader::execute(unsigned int slot, unsigned long long entry) { this->m_slot = slot; this->m_entry = entry; this->m_updated = false; } inline void queryosity::dataset::source::initialize() {} inline void queryosity::dataset::source::initialize(unsigned int, unsigned long long, unsigned long long) {} inline void queryosity::dataset::source::execute(unsigned int, unsigned long long) {} inline void queryosity::dataset::source::finalize(unsigned int) {} inline void queryosity::dataset::source::finalize() {} template template std::unique_ptr> queryosity::dataset::reader::read_column(unsigned int slot, const std::string &name) { auto col_rdr = static_cast(this)->template read(slot, name); if (!col_rdr) throw std::runtime_error("dataset column cannot be read"); return col_rdr; } template template std::unique_ptr> queryosity::dataset::reader::read(unsigned int, const std::string &) { return nullptr; } #include #include namespace queryosity { /** * @brief Calculate a column value for each dataset entry. * @tparam Val Column value type. * @details A calculation is performed once per-entry (if needed) and its value * is stored for multiple accesses by downstream actions within the entry. * The type `Val` must be *CopyConstructible* and *CopyAssignable*. */ template class column::calculation : public valued { public: calculation(); virtual ~calculation() = default; protected: template calculation(Args &&...args); public: virtual const Val &value() const final override; virtual Val calculate() const = 0; virtual void initialize(unsigned int slot, unsigned long long begin, unsigned long long end) override; virtual void execute(unsigned int slot, unsigned long long entry) override; virtual void finalize(unsigned int slot) override; protected: void update() const; void reset() const; protected: mutable Val m_value; mutable bool m_updated; }; } // namespace queryosity template queryosity::column::calculation::calculation() : m_value(), m_updated(false) {} template template queryosity::column::calculation::calculation(Args &&...args) : m_value(std::forward(args)...) {} template const Val &queryosity::column::calculation::value() const { if (!m_updated) this->update(); return m_value; } template void queryosity::column::calculation::update() const { m_value = std::move(this->calculate()); m_updated = true; } template void queryosity::column::calculation::reset() const { m_updated = false; } template void queryosity::column::calculation::initialize(unsigned int, unsigned long long, unsigned long long) {} template void queryosity::column::calculation::execute(unsigned int, unsigned long long) { this->reset(); } template void queryosity::column::calculation::finalize(unsigned int) {} #include #include #include namespace queryosity { namespace column { template class evaluator { public: using evaluated_type = T; public: template evaluator(Args const &...args); virtual ~evaluator() = default; template std::unique_ptr evaluate(view const &...cols) const; protected: std::function()> m_make; }; } // namespace column } // namespace queryosity template template queryosity::column::evaluator::evaluator(Args const &...args) : m_make([args...]() { return std::make_unique(args...); }) {} template template std::unique_ptr queryosity::column::evaluator::evaluate(view const &...columns) const { auto defn = m_make(); defn->set_arguments(columns...); return defn; } #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace queryosity { namespace dataset { using entry_t = unsigned long long; using partition_t = std::vector; using slot_t = unsigned int; namespace partition { partition_t align(std::vector const &partitions); partition_t truncate(partition_t const &parts, long long nentries_max); } // namespace partition } // namespace dataset } // namespace queryosity inline queryosity::dataset::partition_t queryosity::dataset::partition::align( std::vector const &partitions) { std::map edge_counts; const unsigned int num_vectors = partitions.size(); // Count appearances of each edge for (auto const &vec : partitions) { std::map seen_edges; // Ensure each edge is only counted once per vector for (auto const &p : vec) { if (seen_edges.find(p.first) == seen_edges.end()) { edge_counts[p.first]++; seen_edges[p.first] = true; } if (seen_edges.find(p.second) == seen_edges.end()) { edge_counts[p.second]++; seen_edges[p.second] = true; } } } // Filter edges that appear in all vectors std::vector aligned_edges; for (auto const &pair : edge_counts) { if (pair.second == num_vectors) { aligned_edges.push_back(pair.first); } } // Create aligned vector of pairs std::vector> aligned_ranges; for (size_t i = 0; i < aligned_edges.size() - 1; ++i) { aligned_ranges.emplace_back(aligned_edges[i], aligned_edges[i + 1]); } return aligned_ranges; } inline queryosity::dataset::partition_t queryosity::dataset::partition::truncate( queryosity::dataset::partition_t const &parts, long long nentries_max) { if (nentries_max < 0) return parts; partition_t parts_truncated; for (auto const &part : parts) { auto part_end = nentries_max >= 0 ? std::min(part.first + nentries_max, part.second) : part.second; parts_truncated.emplace_back(part.first, part_end); nentries_max -= (part_end - part.first); if (!nentries_max) break; } return parts_truncated; } #include #include #include #include namespace queryosity { namespace column { class computation { public: computation() = default; virtual ~computation() = default; public: template auto read(dataset::reader &ds, unsigned int slot, const std::string &name) -> read_column_t *; template auto assign(Val const &val) -> fixed *; template auto convert(Col const &col) -> conversion> *; template auto define(Args const &...vars) const -> std::unique_ptr>; template auto equate(std::function fn) const -> std::unique_ptr< evaluator(std::decay_t...)>>>; template auto evaluate(evaluator const&calc, Cols const &...cols) -> Def *; protected: template auto add_column(std::unique_ptr col) -> Col *; protected: std::vector> m_columns; }; } } // namespace queryosity #include #include namespace queryosity { template class column::conversion : public column::definition { public: conversion(view const &from); virtual ~conversion() = default; virtual To evaluate(observable from) const override; }; } // namespace queryosity template queryosity::column::conversion::conversion(view const &from) { this->set_arguments(from); } template To queryosity::column::conversion::evaluate( observable from) const { return from.value(); } #include namespace queryosity { namespace column { template class equation : public definition { public: using vartuple_type = typename definition::vartuple_type; using function_type = std::function(std::decay_t const &...)>; public: template equation(Fn&& fn); virtual ~equation() = default; public: virtual Out evaluate(observable... args) const final override; virtual void initialize(unsigned int slot, unsigned long long begin, unsigned long long end) final override; virtual void execute(unsigned int slot, unsigned long long entry) final override; virtual void finalize(unsigned int slot) final override; protected: function_type m_evaluate; }; } // namespace column } // namespace queryosity template template queryosity::column::equation::equation(Fn&& fn) : m_evaluate(std::forward(fn)) {} template Out queryosity::column::equation::evaluate( observable... args) const { return this->m_evaluate(args.value()...); } template void queryosity::column::equation::initialize(unsigned int slot, unsigned long long begin, unsigned long long end) { calculation::initialize(slot, begin, end); } template void queryosity::column::equation::execute(unsigned int slot, unsigned long long entry) { calculation::execute(slot, entry); } template void queryosity::column::equation::finalize(unsigned int slot) { calculation::finalize(slot); } namespace queryosity { //------------------------------------------------------------------------------ // fixed: value set manually //------------------------------------------------------------------------------ template class column::fixed : public valued { public: fixed(Val const &val); template fixed(Args &&...args); virtual ~fixed() = default; const Val &value() const final override; virtual void initialize(unsigned int slot, unsigned long long begin, unsigned long long end) final override; virtual void execute(unsigned int slot, unsigned long long entry) final override; virtual void finalize(unsigned int slot) final override; protected: Val m_value; }; } // namespace queryosity template queryosity::column::fixed::fixed(Val const &val) : m_value(val) {} template template queryosity::column::fixed::fixed(Args &&...args) : m_value(std::forward(args)...) {} template const Val &queryosity::column::fixed::value() const { return m_value; } template void queryosity::column::fixed::initialize(unsigned int slot, unsigned long long begin, unsigned long long end) { valued::initialize(slot, begin, end); } template void queryosity::column::fixed::execute(unsigned int slot, unsigned long long entry) { valued::execute(slot, entry); } template void queryosity::column::fixed::finalize(unsigned int slot) { valued::finalize(slot); } template auto queryosity::column::computation::read(dataset::reader &ds, unsigned int slot, const std::string &name) -> read_column_t * { auto rdr = ds.template read_column(slot, name); return this->add_column(std::move(rdr)); } template auto queryosity::column::computation::assign(Val const &val) -> fixed * { auto cnst = std::make_unique>(val); return this->add_column(std::move(cnst)); } template auto queryosity::column::computation::convert(Col const &col) -> conversion> * { auto cnv = std::make_unique>>(col); cnv->set_arguments(col); return this->add_column(std::move(cnv)); } template auto queryosity::column::computation::define(Args const &...args) const -> std::unique_ptr> { return std::make_unique>(args...); } template auto queryosity::column::computation::equate(std::function fn) const -> std::unique_ptr< evaluator(std::decay_t...)>>> { return std::make_unique< evaluator(std::decay_t...)>>>( fn); } template auto queryosity::column::computation::evaluate(evaluator const &calc, Cols const &...cols) -> Def * { auto defn = calc.evaluate(cols...); return this->add_column(std::move(defn)); } template auto queryosity::column::computation::add_column(std::unique_ptr col) -> Col * { auto out = col.get(); m_columns.push_back(std::move(col)); return out; } #include #include #include #include #include #include #include #include namespace queryosity { /** * @brief Apply cuts and weights to entries. */ namespace selection { class cutflow; class cut; class weight; template class applicator; struct count_t; class counter; template struct yield; class node : public column::calculation { public: node(const selection::node *presel, column::variable dec); virtual ~node() = default; public: bool is_initial() const noexcept; const selection::node *get_previous() const noexcept; virtual bool passed_cut() const = 0; virtual double get_weight() const = 0; virtual void initialize(unsigned int slot, unsigned long long begin, unsigned long long end) final override; virtual void execute(unsigned int slot, unsigned long long entry) final override; virtual void finalize(unsigned int slot) final override; protected: const selection::node *const m_preselection; column::variable m_decision; }; template struct is_applicable : std::false_type {}; template struct is_applicable> : std::true_type {}; template static constexpr bool is_applicable_v = is_applicable::value; template using applied_t = typename T::selection_type; } // namespace selection template constexpr bool is_selection_v = std::is_base_of_v; } // namespace queryosity #include #include #include #include #include /** * @brief All queryosity namespaces and classes. */ namespace queryosity { namespace column { template class view; template class valued; template class variable; template class observable; } // namespace column namespace selection { class node; } /** * @brief Perform a query. */ namespace query { class experiment; template class aggregation; template class fillable; template class definition; template class booker; template class series; template class calculation; template struct output; class node : public action { public: node(); virtual ~node() = default; void apply_scale(double scale); void use_weight(bool use = true); void set_selection(const selection::node &selection); const selection::node *get_selection() const; virtual void initialize(unsigned int slot, unsigned long long begin, unsigned long long end) override; virtual void execute(unsigned int slot, unsigned long long entry) final override; virtual void finalize(unsigned int slot) override; virtual void count(double w) = 0; protected: double m_scale; const selection::node *m_selection; }; template constexpr std::true_type check_implemented(query::aggregation const &); constexpr std::false_type check_implemented(...); template constexpr std::true_type check_fillable(query::fillable const &); constexpr std::false_type check_fillable(...); template struct is_bookable : std::false_type {}; template struct is_bookable> : std::true_type {}; template constexpr bool is_aggregation_v = decltype(check_implemented(std::declval()))::value; template constexpr bool is_fillable_v = decltype(check_fillable(std::declval()))::value; template constexpr bool is_bookable_v = is_bookable::value; template using booked_t = typename Bkr::booked_type; // mixin class to conditionally add a member variable template struct result_of {}; // Specialization for types satisfying is_query template struct result_of>> { using result_type = decltype(std::declval().result()); result_of() : m_merged(false) {} virtual ~result_of() = default; protected: result_type m_result; bool m_merged; }; } // namespace query } // namespace queryosity inline queryosity::query::node::node() : m_scale(1.0), m_selection(nullptr) {} inline void queryosity::query::node::set_selection(const selection::node &selection) { m_selection = &selection; } inline const queryosity::selection::node * queryosity::query::node::get_selection() const { return m_selection; } inline void queryosity::query::node::apply_scale(double scale) { m_scale *= scale; } inline void queryosity::query::node::initialize(unsigned int, unsigned long long, unsigned long long) { if (!m_selection) throw std::runtime_error("no booked selection"); } inline void queryosity::query::node::execute(unsigned int, unsigned long long) { if (m_selection->passed_cut()) { this->count(m_scale * m_selection->get_weight()); } } inline void queryosity::query::node::finalize(unsigned int) {} inline queryosity::selection::node::node(const selection::node *presel, column::variable dec) : m_preselection(presel), m_decision(std::move(dec)) {} inline bool queryosity::selection::node::is_initial() const noexcept { return m_preselection ? false : true; } inline const queryosity::selection::node * queryosity::selection::node::get_previous() const noexcept { return m_preselection; } inline void queryosity::selection::node::initialize(unsigned int slot, unsigned long long begin, unsigned long long end) { column::calculation::initialize(slot, begin, end); } inline void queryosity::selection::node::execute(unsigned int slot, unsigned long long entry) { column::calculation::execute(slot, entry); } inline void queryosity::selection::node::finalize(unsigned int slot) { column::calculation::finalize(slot); } #include #include #include namespace queryosity { template class selection::applicator : public column::evaluator { public: using selection_type = Sel; using evaluated_type = typename column::evaluator::evaluated_type; public: template applicator(selection::node const *prev, Args const &...args); virtual ~applicator() = default; template std::pair, std::unique_ptr> apply(column::view const &...columns) const; protected: selection::node const *m_prev; }; } // namespace queryosity template template queryosity::selection::applicator::applicator( selection::node const *prev, Args const &...args) : column::evaluator(args...), m_prev(prev) {} template template std::pair, std::unique_ptr> queryosity::selection::applicator::apply( column::view const &...columns) const { auto col = this->evaluate(columns...); auto sel = std::make_unique(m_prev, column::variable(*col)); return {std::move(sel), std::move(col)}; } namespace queryosity { class selection::cutflow : public column::computation { public: cutflow() = default; ~cutflow() = default; public: template auto apply(selection::node const *prev, column::valued const &dec) -> selection::node *; template auto select(selection::node const *prev, std::function fn) const -> std::unique_ptr(std::decay_t...)>>>; template auto apply(applicator const &calc, Cols const &...cols) -> selection::node *; protected: template auto add_selection(std::unique_ptr selection) -> Sel *; protected: std::vector> m_selections; }; } // namespace queryosity namespace queryosity { class selection::cut : public selection::node { public: cut(const selection::node *presel, column::variable dec); virtual ~cut() = default; public: virtual double calculate() const final override; virtual bool passed_cut() const final override; virtual double get_weight() const final override; }; } // namespace queryosity inline queryosity::selection::cut::cut(const selection::node *presel, column::variable dec) : selection::node(presel, std::move(dec)) {} inline double queryosity::selection::cut::calculate() const { return this->passed_cut(); } inline bool queryosity::selection::cut::passed_cut() const { return this->m_preselection ? this->m_preselection->passed_cut() && m_decision.value() : m_decision.value(); } inline double queryosity::selection::cut::get_weight() const { return this->m_preselection ? this->m_preselection->get_weight() : 1.0; } namespace queryosity { class selection::weight : public selection::node { public: class a_times_b; public: weight(const selection::node *presel, column::variable dec); virtual ~weight() = default; public: virtual double calculate() const final override; virtual bool passed_cut() const final override; virtual double get_weight() const final override; }; } // namespace queryosity inline queryosity::selection::weight::weight(const selection::node *presel, column::variable dec) : selection::node(presel, std::move(dec)) {} inline double queryosity::selection::weight::calculate() const { return this->get_weight(); } inline bool queryosity::selection::weight::passed_cut() const { return this->m_preselection ? this->m_preselection->passed_cut() : true; } inline double queryosity::selection::weight::get_weight() const { return this->m_preselection ? this->m_preselection->get_weight() * m_decision.value() : m_decision.value(); } template auto queryosity::selection::cutflow::apply(selection::node const *prev, column::valued const &dec) -> selection::node * { auto sel = std::make_unique(prev, column::variable(dec)); return this->add_selection(std::move(sel)); } template auto queryosity::selection::cutflow::select( selection::node const *prev, std::function fn) const -> std::unique_ptr(std::decay_t...)>>> { return std::make_unique(std::decay_t...)>>>(prev, fn); } template auto queryosity::selection::cutflow::apply( selection::applicator const &calc, Cols const &...cols) -> selection::node * { auto [sel, col] = calc.apply(cols...); this->add_column(std::move(col)); return this->add_selection(std::move(sel)); } template auto queryosity::selection::cutflow::add_selection(std::unique_ptr sel) -> Sel * { auto out = sel.get(); m_selections.push_back(std::move(sel)); return out; } #include #include #include namespace queryosity { template class query::booker { public: using booked_type = T; public: template booker(Args... args); ~booker() = default; // copyable booker(const booker &) = default; booker &operator=(const booker &) = default; template auto add_columns(column::valued const &...cols) const -> std::unique_ptr>; auto set_selection(const selection::node &sel) const -> std::unique_ptr; protected: std::unique_ptr make_query(); template void fill_query(column::valued const &...cols); protected: std::function()> m_make_unique_query; std::vector> m_add_columns; }; } // namespace queryosity template template queryosity::query::booker::booker(Args... args) : m_make_unique_query(std::bind( [](Args... args) { return std::make_unique(args...); }, args...)) { } template template auto queryosity::query::booker::add_columns( column::valued const &...columns) const -> std::unique_ptr> { // use a fresh one with its current fills auto filled = std::make_unique>(*this); // add fills filled->fill_query(columns...); // return new book return filled; } template template void queryosity::query::booker::fill_query( column::valued const &...columns) { // use a snapshot of its current calls m_add_columns.push_back(std::bind( [](T &cnt, column::valued const &...cols) { cnt.enter_columns(cols...); }, std::placeholders::_1, std::cref(columns)...)); } template auto queryosity::query::booker::set_selection(const selection::node &sel) const -> std::unique_ptr { // call constructor auto cnt = m_make_unique_query(); // fill columns (if set) for (auto const &fill_query : m_add_columns) { fill_query(*cnt); } // book cnt at the selection cnt->set_selection(sel); return cnt; } namespace queryosity { class query::experiment : public selection::cutflow { public: experiment() = default; ~experiment() = default; public: template std::unique_ptr> make(Args &&...args); template auto book(query::booker const &bkr, const selection::node &sel) -> Qry *; protected: template auto add_query(std::unique_ptr qry) -> Qry *; protected: std::vector m_queries; std::vector> m_queries_history; }; } // namespace queryosity template std::unique_ptr> queryosity::query::experiment::make(Args &&...args) { auto bkr = std::make_unique>(std::forward(args)...); return bkr; } template auto queryosity::query::experiment::book(query::booker const &bkr, const selection::node &sel) -> Qry * { auto qry = bkr.set_selection(sel); return this->add_query(std::move(qry)); } template auto queryosity::query::experiment::add_query(std::unique_ptr qry) -> Qry * { auto out = qry.get(); m_queries_history.push_back(std::move(qry)); m_queries.push_back(m_queries_history.back().get()); return out; } namespace queryosity { namespace dataset { class player : public query::experiment { public: player() = default; virtual ~player() = default; public: void play(std::vector> const &sources, double scale, slot_t slot, std::vector const &parts); }; } // namespace dataset } // namespace queryosity inline void queryosity::dataset::player::play( std::vector> const &sources, double scale, slot_t slot, std::vector const &parts) { // apply dataset scale in effect for all queries for (auto const &qry : m_queries) { qry->apply_scale(scale); } // traverse each part for (auto const &part : parts) { // initialize for (auto const &ds : sources) { ds->initialize(slot, part.first, part.second); } for (auto const &col : m_columns) { col->initialize(slot, part.first, part.second); } for (auto const &sel : m_selections) { sel->initialize(slot, part.first, part.second); } for (auto const &qry : m_queries) { qry->initialize(slot, part.first, part.second); } // execute for (auto entry = part.first; entry < part.second; ++entry) { for (auto const &ds : sources) { ds->execute(slot, entry); } for (auto const &col : m_columns) { col->execute(slot, entry); } for (auto const &sel : m_selections) { sel->execute(slot, entry); } for (auto const &qry : m_queries) { qry->execute(slot, entry); } } // finalize (in reverse order) for (auto const &qry : m_queries) { qry->finalize(slot); } for (auto const &sel : m_selections) { sel->finalize(slot); } for (auto const &col : m_columns) { col->finalize(slot); } for (auto const &ds : sources) { ds->finalize(slot); } } // clear out queries (should not be re-played) m_queries.clear(); } namespace queryosity { namespace dataset { class processor : public multithread::core, public ensemble::slotted { public: processor(int suggestion); virtual ~processor() = default; processor(const processor &) = delete; processor &operator=(const processor &) = delete; processor(processor &&) noexcept = default; processor &operator=(processor &&) noexcept = default; template auto read(dataset::reader &ds, const std::string &column_name) -> std::vector *>; void downsize(unsigned int nslots); void process(std::vector> const &sources, double scale, unsigned long long nrows); virtual std::vector const &get_slots() const override; protected: std::vector m_range_slots; std::vector m_players; std::vector m_player_ptrs; }; } // namespace dataset namespace multithread { dataset::processor enable(int suggestion = -1); dataset::processor disable(); } // namespace multithread } // namespace queryosity inline queryosity::dataset::processor queryosity::multithread::enable(int suggestion) { return dataset::processor(suggestion); } inline queryosity::dataset::processor queryosity::multithread::disable() { return dataset::processor(false); } inline queryosity::dataset::processor::processor(int suggestion) : multithread::core::core(suggestion), m_range_slots(), m_players(), m_player_ptrs() { const auto nslots = this->concurrency(); m_players = std::vector(nslots); m_player_ptrs = std::vector(nslots, nullptr); std::transform(m_players.begin(), m_players.end(), m_player_ptrs.begin(), [](player &plyr) -> player * { return &plyr; }); m_range_slots.clear(); m_range_slots.reserve(nslots); for (unsigned int i = 0; i < nslots; ++i) { m_range_slots.push_back(i); } } template auto queryosity::dataset::processor::read(dataset::reader &ds, const std::string &column_name) -> std::vector *> { return ensemble::invoke( [column_name, &ds](dataset::player *plyr, unsigned int slot) { return plyr->template read(ds, slot, column_name); }, m_player_ptrs, m_range_slots); } inline void queryosity::dataset::processor::downsize(unsigned int nslots) { if (nslots > this->size()) { throw std::logic_error("requested thread count too high"); }; while (m_players.size() > nslots) { // avoid copy-constructor m_players.pop_back(); } m_player_ptrs.resize(nslots); m_range_slots.resize(nslots); } inline void queryosity::dataset::processor::process( std::vector> const &sources, double scale, unsigned long long nrows) { const auto nslots = this->concurrency(); // 1. enter event loop for (auto const &ds : sources) { ds->initialize(); } // 2. partition dataset(s) // 2.1 get partition from each dataset source std::vector partitions_from_sources; for (auto const &ds : sources) { auto partition_from_source = ds->partition(); if (partition_from_source.size()) partitions_from_sources.push_back(std::move(partition_from_source)); } if (!partitions_from_sources.size()) { throw std::runtime_error("no valid dataset partition found"); } // 2.2 find common denominator partition const auto partition_aligned = dataset::partition::align(partitions_from_sources); // 2.3 truncate entries to row limit const auto partition_truncated = dataset::partition::truncate(partition_aligned, nrows); // 2.3 distribute partition amongst threads std::vector partitions_for_slots(nslots); auto nparts_remaining = partition_truncated.size(); const auto nparts = nparts_remaining; while (nparts_remaining) { for (unsigned int islot = 0; islot < nslots; ++islot) { partitions_for_slots[islot].push_back( std::move(partition_truncated[nparts - (nparts_remaining--)])); if (!nparts_remaining) break; } } // todo: can intel tbb distribute slots during parallel processing? // 3. run event loop this->run( [&sources, scale]( dataset::player *plyr, unsigned int slot, std::vector> const &parts) { plyr->play(sources, scale, slot, parts); }, m_player_ptrs, m_range_slots, partitions_for_slots); // 4. exit event loop for (auto const &ds : sources) { ds->finalize(); } } inline std::vector const & queryosity::dataset::processor::get_slots() const { return m_player_ptrs; } namespace queryosity { template class lazy; template class todo; template class varied; /** * @ingroup api * @brief Main dataflow interface. */ class dataflow { public: template friend class dataflow::input; template friend class lazy; template friend class todo; template friend class varied; public: class node; public: /** * @brief Default constructor. */ dataflow(); ~dataflow() = default; template dataflow(Kwd &&kwarg); template dataflow(Kwd1 &&kwarg1, Kwd2 &&kwarg2); /** * @brief Constructor with (up to) three keyword arguments. * @details Each keyword argument should be one of the following: * * - `queryosity::multithread::enable(unsigned int)` * - `queryosity::multithread::disable()` * - `queryosity::dataset::head(unsigned int)` * - `queryosity::dataset::weight(float)` * */ template dataflow(Kwd1 &&kwarg1, Kwd2 &&kwarg2, Kwd3 &&kwarg3); dataflow(dataflow const &) = delete; dataflow &operator=(dataflow const &) = delete; dataflow(dataflow &&) = default; dataflow &operator=(dataflow &&) = default; /** * @brief Load a dataset input. * @tparam DS `dataset::reader` implementation. * @tparam Args... Constructor arguments. * @return Loaded dataset. * @warning A dataset should *not* be loaded in more than once. Doing so * incurs an I/O overhead at best, and a potential thread-unsafe data race at * worst(as an entry will be read out multiple times concurrently). */ template auto load(dataset::input &&in) -> dataflow::input; /** * @brief Read a column from an input dataset. * @attention A dataset should be loaded-in *once*. Use this method only if * you are interested in the requested column, as other columns will not be * readable. * @tparam DS `dataset::reader` implementation. * @tparam Val Column data type. * @param[in] Column name. * @return Column read from the loaded dataset. */ template auto read(dataset::input in, dataset::column const &col); /** * @brief Read columns from an input dataset. * @attention A dataset should be loaded-in *once*. Use this method only if * you are interested in the requested columns, as other columns will not be * readable. * @tparam DS `dataset::reader` implementation. * @tparam Vals Column data types. * @param[in] cols Column names. * @return Columns read from the loaded dataset. */ template auto read(dataset::input in, dataset::column const &...cols); /** * @brief Define a constant column. * @tparam Val Column data type. * @param[in] cnst Constant value. */ template auto define(column::constant const &cnst) -> lazy>; /** * @brief Define a column using an expression. * @tparam Fn Callable type. * @param[in] expr C++ function, functor, lambda, or any other callable. * @return Evaluator. */ template auto define(column::expression const &expr) -> todo>>; /** * @brief Define a column using an expression. * @tparam Def Custom definition. * @param[in] defn Definition type and constructor arguments. * @return Evaluator. */ template auto define(column::definition const &defn) -> todo>; /** * @brief Initiate a cutflow. * @tparam Col Column type. * @param[in] column Input column used as cut decision. * @return Lazy selection. */ template auto filter(lazy const &column) -> lazy; /** * @brief Initiate a cutflow. * @tparam Col Column type. * @param[in] column Input column used as weight decision. * @return Lazy selection. */ template auto weight(lazy const &column) -> lazy; /** * @brief Initiate a cutflow. * @tparam Col Lazy varied column. * @param[in] column Input column used as cut decision. * @return Lazy varied selection. */ template auto filter(varied const &col); /** * @brief Initiate a cutflow. * @tparam Col Lazy varied column. * @param[in] column Input column used as weight decision. * @return Lazy varied selection. */ template auto weight(varied const &col); /** * @brief Initiate a cutflow. * @tparam Fn C++ Callable object. * @tparam Cols Column types. * @param[in] Input (varied) columns used to evaluate cut decision. * @return Lazy (varied) selection. */ template auto filter(column::constant const &expr) -> lazy; /** * @brief Initiate a cutflow. * @tparam Fn C++ Callable object. * @tparam Cols Column types. * @param[in] Input (varied) columns used to evaluate cut decision. * @return Lazy (varied) selection. */ template auto weight(column::constant const &expr) -> lazy; /** * @brief Initiate a cutflow. * @tparam Fn C++ Callable object. * @tparam Cols Column types. * @param[in] Input (varied) columns used to evaluate cut decision. * @return Lazy (varied) selection. */ template auto filter(column::expression const &expr) -> todo>>; /** * @brief Initiate a cutflow. * @tparam Fn C++ Callable object. * @tparam Cols Column types. * @param[in] Input (varied) columns used to evaluate weight decision. * @return Lazy (varied) selection. */ template auto weight(column::expression const &expr) -> todo>>; /** * @brief Plan a query. * @tparam Qry Concrete queryosity::query::definition implementation. * @param[in] output Query output (constructor arguments). * @return queryosity::todo query booker. */ template auto get(query::output const &output) -> todo>; /** * @brief Get a column series. * @tparam Col (Varied) lazy column. * @param[in] col Column as series constructor argument. * @return (Varied) lazy column series query. */ template auto get(column::series const &col); /** * @brief Get selection yield. * @tparam Sels (Varied) lazy selection(s). * @param[in] sel Selection(s) as yield constructor argument(s). * @return (Varied) lazy selection yield query(ies). */ template auto get(selection::yield const &sels); /** * @brief Vary a column constant. * @tparam Val Constant value type. * @param[in] cnst Column constant. * @param[in] vars Map of variation to value. * @return Varied lazy column. */ template auto vary(column::constant const &cnst, std::map vars) -> varied>>; /** * @brief Vary a column expression. * @tparam Fn Expression function type. * @param[in] expr Column expression. * @param[in] vars Map of variation to expression. * @return Varied todo column evaluator. */ template auto vary(column::expression const &expr, std::map::function_type> const &vars) -> varied>>>; /** * @brief Vary a column definition. * @tparam Def Definition type. * @param[in] defn Column definition. * @param[in] vars Map of variation to definition. * @return Varied todo column evaluator. */ template auto vary(column::definition const &defn, std::map> const &vars) -> varied>>; /** * @brief Vary a column. * @tparam Col Column type. * @param[in] nom Nominal lazy column. * @param[in] vars Map of variation to lazy column. * @return Varied lazy column. */ template auto vary(column::nominal const &nom, std::map>> const &vars) -> varied>>>; /* "public" API for Python layer */ template auto _convert(lazy const &col) -> lazy>>; template auto _assign(Val const &val) -> lazy>; template auto _cut(lazy const &column) -> lazy; template auto _cut(varied const &column) -> varied>; template auto _define(Args &&...args); template auto _define(column::definition const &defn) -> todo>; template auto _equate(Fn fn); template auto _equate(column::expression const &expr) -> todo>>; template auto _select(Fn fn); template auto _select(column::expression const &expr) -> todo>>; template auto _select(lazy const &prev, Fn fn); template auto _select(lazy const &prev, column::expression const &expr) -> todo>>; template auto _make(Args &&...args) -> todo>; protected: template void accept_kwarg(Kwd &&kwarg); void analyze(); void reset(); template auto _read(dataset::reader &ds, const std::string &name) -> lazy>; template auto _evaluate(todo> const &calc, lazy const &...columns) -> lazy; template auto _apply(lazy const &col) -> lazy; template auto _apply(lazy const &prev, lazy const &col) -> lazy; template auto _apply(todo> const &calc, lazy const &...columns) -> lazy; template auto _book(todo> const &bkr, lazy const &sel) -> lazy; template auto _book(todo> const &bkr, lazy const &...sels) -> std::array, sizeof...(Sels)>; template void _vary(Syst &syst, const std::string &name, column::constant const &cnst); template void _vary(Syst &syst, const std::string &name, column::expression const &expr); template void _vary(Syst &syst, const std::string &name, column::definition const &defn); protected: dataset::processor m_processor; dataset::weight m_weight; long long m_nrows; std::vector> m_sources; std::vector m_dslots; bool m_analyzed; }; class dataflow::node { public: friend class dataflow; template friend class varied; public: template static auto invoke(Fn fn, Nodes const &...nodes) -> std::enable_if_t< !std::is_void_v< std::invoke_result_t>, std::vector>>; template static auto invoke(Fn fn, Nodes const &...nodes) -> std::enable_if_t>, void>; public: node(dataflow &df); virtual ~node() = default; protected: dataflow *m_df; }; } // namespace queryosity #include namespace queryosity { namespace dataset { /** * @ingroup api * @brief Argument to load a dataset input in the dataflow. * @tparam DS Concrete implementation of `queryosity::dataset::reader`. */ template struct input { /** * @brief Argument constructor. * @tparam Args Constructor arguments types for @p DS. * @param[in] args Constructor arguments for @p DS. */ input() = default; template input(Args &&...args); virtual ~input() = default; std::unique_ptr ds; //! }; } // namespace dataset } // namespace queryosity template template queryosity::dataset::input::input(Args &&...args) : ds(std::make_unique(std::forward(args)...)) {} #include namespace queryosity { namespace dataset { template class loaded { public: loaded(dataflow &df, DS &ds); ~loaded() = default; template auto _read(const std::string &name) { return m_df->_read(*m_ds, name); } template auto read(dataset::column const &col) -> lazy>; template auto read(dataset::column const &...cols); template auto vary(dataset::column const &col, std::map const &vars); protected: dataflow *m_df; dataset::reader *m_ds; }; } // namespace dataset } // namespace queryosity #include #include #include #include namespace queryosity { class dataflow; /** * @ingroup api * @brief Argument to read a column from a loaded dataset. * @tparam Val Column data type. */ template class dataset::column { public: /** * @brief Argument constructor. * @param[in] column_name Name of column. */ column(const std::string &column_name); ~column() = default; template auto _read(dataflow::input &ds) const; protected: std::string m_name; }; } // namespace queryosity template queryosity::dataset::column::column(const std::string &name) : m_name(name) {} template template auto queryosity::dataset::column::_read( queryosity::dataflow::input &ds) const { return ds.template _read(this->m_name); } #include #include #include #include #include #include // check for the existence of a native/custom binary operator #define CHECK_FOR_BINARY_OP(op_name, op_symbol) \ template \ struct has_native_##op_name : std::false_type {}; \ \ template \ struct has_native_##op_name< \ T, Arg, \ std::void_t() op_symbol std::declval())>> \ : std::true_type {}; \ \ template \ auto operator op_symbol(const T &, const Arg &) -> priority_tag<0>; \ \ template struct has_custom_only_##op_name { \ private: \ template \ static auto test(priority_tag<1>) \ -> decltype(std::declval() op_symbol std::declval(), \ std::true_type()); \ \ template \ static auto test(priority_tag<0>) -> std::false_type; \ \ public: \ static constexpr bool value = \ decltype(test(priority_tag<1>()))::value && \ !has_native_##op_name::value; \ }; #define DEFINE_LAZY_BINARY_OP(op_name, op_symbol) \ template && \ queryosity::is_column_v && \ (detail::has_native_##op_name< \ column::value_t, \ column::value_t>::value || \ detail::has_custom_only_##op_name< \ column::value_t, \ column::value_t>::value), \ bool>::type = true> \ auto operator op_symbol(Arg const &arg) const { \ return this->m_df \ ->define(queryosity::column::expression( \ [](column::value_t const &me, \ column::value_t const &you) { \ return me op_symbol you; \ })) \ .template evaluate(*this, arg); \ } #define CHECK_FOR_UNARY_OP(op_name, op_symbol) \ struct has_no_##op_name {}; \ template has_no_##op_name operator op_symbol(const T &); \ template struct has_##op_name { \ enum { \ value = !std::is_same()), \ has_no_##op_name>::value \ }; \ }; \ template \ static constexpr bool has_##op_name##_v = has_##op_name::value; #define DEFINE_LAZY_UNARY_OP(op_name, op_symbol) \ template < \ typename V = Action, \ std::enable_if_t && \ detail::has_##op_name##_v>, \ bool> = false> \ auto operator op_symbol() const { \ return this->m_df \ ->define(queryosity::column::expression( \ [](column::value_t const &me) { return (op_symbol me); })) \ .template evaluate(*this); \ } #define CHECK_FOR_INDEX_OP() \ template struct has_subscript_impl { \ template ())[std::declval()]), \ typename = typename std::enable_if< \ !std::is_void::value>::type> \ static std::true_type test(int); \ template static std::false_type test(...); \ using type = decltype(test(0)); \ }; \ template \ using has_subscript = typename has_subscript_impl::type; \ template \ static constexpr bool has_subscript_v = has_subscript::value; #define DEFINE_LAZY_INDEX_OP() \ template < \ typename Arg, typename V = Action, \ std::enable_if_t && \ detail::has_subscript_v< \ column::value_t, \ column::value_t>, \ bool> = false> \ auto operator[](Arg const &arg) const { \ return this->m_df \ ->define(queryosity::column::expression( \ [](column::value_t me, \ column::value_t index) { \ return me[index]; \ })) \ .template evaluate(*this, arg); \ } #define DECLARE_LAZY_VARIED_BINARY_OP(op_symbol) \ template < \ typename Arg, typename V = Act, \ std::enable_if_t && \ queryosity::is_column_v, \ bool> = false> \ auto operator op_symbol(Arg const &b) const -> varied< \ lazy>(). \ operator op_symbol(b.nominal()))::action_type>>; #define DEFINE_LAZY_VARIED_BINARY_OP(op_symbol) \ template \ template < \ typename Arg, typename V, \ std::enable_if_t && \ queryosity::is_column_v, \ bool>> \ auto queryosity::varied>::operator op_symbol( \ Arg const &b) const \ -> varied< \ lazy>().operator op_symbol( \ b.nominal()))::action_type>> { \ auto syst = varied< \ lazy>().operator op_symbol( \ b.nominal()))::action_type>>( \ this->nominal().operator op_symbol(b.nominal())); \ for (auto const &var_name : systematic::get_variation_names(*this, b)) { \ syst.set_variation(var_name, variation(var_name).operator op_symbol( \ b.variation(var_name))); \ } \ return syst; \ } #define DECLARE_LAZY_VARIED_UNARY_OP(op_symbol) \ template , bool> = false> \ auto operator op_symbol() const -> varied< \ queryosity::lazy>(). \ operator op_symbol())::action_type>>; #define DEFINE_LAZY_VARIED_UNARY_OP(op_name, op_symbol) \ template \ template , bool>> \ auto queryosity::varied>::operator op_symbol() const \ -> varied>(). \ operator op_symbol())::action_type>> { \ auto syst = varied>(). \ operator op_symbol())::action_type>>( \ this->nominal().operator op_symbol()); \ for (auto const &var_name : systematic::get_variation_names(*this)) { \ syst.set_variation(var_name, variation(var_name).operator op_symbol()); \ } \ return syst; \ } namespace queryosity { template class lazy; template class todo; template static constexpr std::true_type check_lazy(lazy const &); static constexpr std::false_type check_lazy(...) { return std::false_type{}; } template static constexpr std::true_type check_todo(todo const &); static constexpr std::false_type check_todo(...) { return std::false_type{}; } template static constexpr bool is_nominal_v = (decltype(check_lazy(std::declval()))::value || decltype(check_todo(std::declval()))::value); template static constexpr bool is_varied_v = !is_nominal_v; template static constexpr bool has_no_variation_v = (is_nominal_v && ...); template static constexpr bool has_variation_v = (is_varied_v || ...); namespace detail { // https://quuxplusone.github.io/blog/2021/07/09/priority-tag/ template struct priority_tag : priority_tag {}; template <> struct priority_tag<0> {}; CHECK_FOR_UNARY_OP(logical_not, !) CHECK_FOR_UNARY_OP(minus, -) CHECK_FOR_BINARY_OP(addition, +) CHECK_FOR_BINARY_OP(subtraction, -) CHECK_FOR_BINARY_OP(multiplication, *) CHECK_FOR_BINARY_OP(division, /) CHECK_FOR_BINARY_OP(remainder, %) CHECK_FOR_BINARY_OP(greater_than, >) CHECK_FOR_BINARY_OP(less_than, <) CHECK_FOR_BINARY_OP(greater_than_or_equal_to, >=) CHECK_FOR_BINARY_OP(less_than_or_equal_to, <=) CHECK_FOR_BINARY_OP(equality, ==) CHECK_FOR_BINARY_OP(inequality, !=) CHECK_FOR_BINARY_OP(logical_and, &&) CHECK_FOR_BINARY_OP(logical_or, ||) CHECK_FOR_BINARY_OP(bitwise_or, &) CHECK_FOR_BINARY_OP(bitwise_and, |) CHECK_FOR_INDEX_OP() } // namespace detail } // namespace queryosity #include #include #include namespace queryosity { class dataflow; template class systematic::resolver { public: using nominal_type = U; public: template friend class lazy; template friend class todo; template friend class resolver; public: resolver() = default; virtual ~resolver() = default; public: virtual void set_variation(const std::string &var_name, U var) = 0; virtual U &nominal() = 0; virtual U &variation(const std::string &var_name) = 0; virtual U const &nominal() const = 0; virtual U const &variation(const std::string &var_name) const = 0; virtual bool has_variation(const std::string &var_name) const = 0; virtual std::set get_variation_names() const = 0; }; } // namespace queryosity namespace queryosity { /** * @ingroup api * @brief Lazy action over dataset. * @tparam Action Action to be performed. */ template class lazy : public dataflow::node, public ensemble::slotted, public systematic::resolver>, public query::result_of { public: using action_type = Action; public: friend class dataflow; template friend class lazy; template friend class varied; template friend struct column::series; // access to dataflow public: lazy(dataflow &df, std::vector const &slots) : dataflow::node(df), m_slots(slots) {} template lazy(dataflow &df, std::vector const &slots); template lazy(dataflow &df, std::vector> const &slots); template operator lazy() const; lazy(const lazy &) = default; lazy &operator=(const lazy &) = default; lazy(lazy &&) = default; lazy &operator=(lazy &&) = default; virtual ~lazy() = default; virtual std::vector const &get_slots() const final override; virtual void set_variation(const std::string &var_name, lazy var) final override; virtual lazy &nominal() final override; virtual lazy &variation(const std::string &var_name) final override; virtual lazy const &nominal() const final override; virtual lazy const & variation(const std::string &var_name) const final override; virtual bool has_variation(const std::string &var_name) const final override; virtual std::set get_variation_names() const final override; template , bool> = false> auto to() const -> lazy>; /** * @brief Compound a weight to from this selection. * @tparam Col Column type. * @param[in] column Input lazy column used as weight decision. * @details The current selection becomes its prerequisite in the cutflow: all * prerequisites must pass in order for a downstream selection to pass. * @return Compounded lazy weight. */ template auto filter(lazy const &column) const; /** * @brief Compound a weight to from this selection. * @tparam Col Column type. * @param[in] column Input lazy column used as weight decision. * @return Compounded lazy weight. */ template auto weight(lazy const &column) const; /** * @brief Compound a varied cut to this selection. * @tparam Col Varied lazy column type. * @param[in] column Input varied column used as cut decision. * @return Varied lazy cut. */ template auto filter(Col const &column) const; /** * @brief Compound a varied weight to this selection. * @tparam Col Varied lazy column type. * @param[in] column Input varied column used as weight decision. * @return Varied lazy weight. */ template auto weight(Col const &column) const; /** * @brief Compound a cut to this selection. * @tparam Expr Callable type (C++ function, functor, lambda, etc.). * @param[in] column Input lazy column used as cut decision. * @return Selection evaluator. */ template auto filter(queryosity::column::expression const &expr) const; /** * @brief Compound a weight to from this selection. * @tparam Expr Callable type (C++ function, functor, lambda, etc.). * @param[in] expr Expression used to evaluate the weight decision. * @return Selection evaluator. */ template auto weight(queryosity::column::expression const &expr) const; /** * @brief Book a query at this selection. * @tparam Qry (Varied) query booker type. * @param[in] qry Query booker. * @details The query booker should have already been filled with input * columns (if applicable). * @return (Varied) lazy query. */ template auto book(Qry &&qry) const; /** * @brief Book multiple queries at this selection. * @tparam Qrys (Varied) query booker types. * @param[in] qrys Query bookers. * @details The query bookers should have already been filled with input * columns (if applicable). * @return (Varied) lazy queries. */ template auto book(Qrys &&...qrys) const; /** * @brief Get a column series for the entries passing the selection. * @tparam Col Lazy column type. * @return Lazy query. * @attention The weight value does not apply in the population of the * series. */ template , bool> = false> auto get(column::series const &col) -> lazy::value_type>>; /** * @brief Get a column series for the entries passing the selection. * @tparam Col Varied column type. * @return Varied lazy query. * @attention The weight value does not apply in the population of the * series. */ template , bool> = false> auto get(column::series const &col) -> varied::value_type>>>; /** * @brief (Process and) retrieve the result of a query. * @return Query result. * @attention Invoking this turns *all* lazy actions in the dataflow *eager*. */ template < typename V = Action, std::enable_if_t, bool> = false> auto result() -> decltype(std::declval().result()); /** * @brief Shortcut for `result()`. */ template < typename V = Action, std::enable_if_t, bool> = false> auto operator->() -> decltype(std::declval().result()) { return this->result(); } DEFINE_LAZY_UNARY_OP(logical_not, !) DEFINE_LAZY_UNARY_OP(minus, -) DEFINE_LAZY_BINARY_OP(equality, ==) DEFINE_LAZY_BINARY_OP(inequality, !=) DEFINE_LAZY_BINARY_OP(addition, +) DEFINE_LAZY_BINARY_OP(subtraction, -) DEFINE_LAZY_BINARY_OP(multiplication, *) DEFINE_LAZY_BINARY_OP(division, /) DEFINE_LAZY_BINARY_OP(logical_and, &&) DEFINE_LAZY_BINARY_OP(logical_or, ||) DEFINE_LAZY_BINARY_OP(bitwise_and, &) DEFINE_LAZY_BINARY_OP(bitwise_or, |) DEFINE_LAZY_BINARY_OP(greater_than, >) DEFINE_LAZY_BINARY_OP(less_than, <) DEFINE_LAZY_BINARY_OP(greater_than_or_equal_to, >=) DEFINE_LAZY_BINARY_OP(less_than_or_equal_to, <=) DEFINE_LAZY_INDEX_OP() protected: template < typename V = Action, std::enable_if_t, bool> = false> void merge_results(); protected: std::vector m_slots; }; } // namespace queryosity #include #include #include #include namespace queryosity { template class varied; /** * @ingroup api * @brief Variations of a lazy action. * @tparam Action Action to be performed. * @details A varied lazy action encapsulates independent nominal and variation * instances of of a lazy action, which are implicitly propagated through all * downstream actions that it participates in. In other words, a varied node * behaves functionally identical to a nominal-only one. */ template class varied> : public dataflow::node, public systematic::resolver> { public: using action_type = typename lazy::action_type; template friend class lazy; template friend class varied; public: varied(lazy nom); virtual ~varied() = default; varied(varied const &) = default; varied &operator=(varied const &) = default; varied(varied &&) = default; varied &operator=(varied &&) = default; template varied(varied> const &); template varied &operator=(varied> const &); virtual void set_variation(const std::string &var_name, lazy var) final override; virtual lazy &nominal() final override; virtual lazy &variation(const std::string &var_name) final override; virtual lazy const &nominal() const final override; virtual lazy const & variation(const std::string &var_name) const final override; virtual bool has_variation(const std::string &var_name) const final override; virtual std::set get_variation_names() const final override; /** * @brief Compound a cut to this selection. * @Col (Varied) lazy input column type. * @parma[in] col (Varied) lazy input column used as cut decision. * @return Varied lazy selection. */ template , bool> = false> auto filter(Col const &col) -> varied>; /** * @brief Compound a weight to this selection. * @Col (Varied) lazy input column type. * @parma[in] col (Varied) lazy input column used as cut decision. * @return Varied lazy selection. */ template , bool> = false> auto weight(Col const &col) -> varied>; template , bool> = false> auto filter(column::expression const &expr) -> varied< todo>>>; template , bool> = false> auto weight(column::expression const &expr) -> varied< todo>>>; template , bool> = false> auto book(Agg &&agg); template , bool> = false> auto book(Aggs &&...aggs); template < typename V = Act, std::enable_if_t, bool> = false> auto operator[](const std::string &var_name) -> lazy &; template < typename V = Act, std::enable_if_t, bool> = false> auto operator[](const std::string &var_name) const -> lazy const &; DECLARE_LAZY_VARIED_UNARY_OP(-) DECLARE_LAZY_VARIED_UNARY_OP(!) DECLARE_LAZY_VARIED_BINARY_OP(+) DECLARE_LAZY_VARIED_BINARY_OP(-) DECLARE_LAZY_VARIED_BINARY_OP(*) DECLARE_LAZY_VARIED_BINARY_OP(/) DECLARE_LAZY_VARIED_BINARY_OP(<) DECLARE_LAZY_VARIED_BINARY_OP(>) DECLARE_LAZY_VARIED_BINARY_OP(<=) DECLARE_LAZY_VARIED_BINARY_OP(>=) DECLARE_LAZY_VARIED_BINARY_OP(==) DECLARE_LAZY_VARIED_BINARY_OP(&&) DECLARE_LAZY_VARIED_BINARY_OP(||) DECLARE_LAZY_VARIED_BINARY_OP([]) protected: lazy m_nom; std::unordered_map> m_var_map; std::set m_var_names; }; } // namespace queryosity template queryosity::varied>::varied(queryosity::lazy nom) : dataflow::node(*nom.m_df), m_nom(std::move(nom)) {} template template queryosity::varied>::varied( varied> const &other) { this->m_df = other.m_df; this->m_var_names = other.m_var_names; for (auto const &var : other.m_var_map) { m_var_map.insert(var); } } template template queryosity::varied> & queryosity::varied>::operator=( varied> const &other) { this->m_df = other.m_df; this->m_var_names = other.m_var_names; for (auto const &var : other.m_var_map) { m_var_map.insert(var); } return *this; } template void queryosity::varied>::set_variation( const std::string &var_name, queryosity::lazy var) { m_var_map.insert(std::make_pair(var_name, std::move(var))); m_var_names.insert(var_name); } template auto queryosity::varied>::nominal() -> queryosity::lazy & { return this->m_nom; } template auto queryosity::varied>::variation( const std::string &var_name) -> queryosity::lazy & { return (this->has_variation(var_name) ? m_var_map.at(var_name) : m_nom); } template auto queryosity::varied>::nominal() const -> queryosity::lazy const & { return this->m_nom; } template auto queryosity::varied>::variation( const std::string &var_name) const -> queryosity::lazy const & { return (this->has_variation(var_name) ? m_var_map.at(var_name) : m_nom); } template bool queryosity::varied>::has_variation( const std::string &var_name) const { return m_var_map.find(var_name) != m_var_map.end(); } template std::set queryosity::varied>::get_variation_names() const { return m_var_names; } template template , bool>> auto queryosity::varied>::filter(Col const &col) -> varied> { using varied_type = varied>; auto syst = varied_type(this->nominal().filter(col.nominal())); for (auto const &var_name : systematic::get_variation_names(*this, col)) { syst.set_variation( var_name, this->variation(var_name).filter(col.variation(var_name))); } return syst; } template template , bool>> auto queryosity::varied>::weight(Col const &col) -> varied> { using varied_type = varied>; auto syst = varied_type(this->nominal().weight(col.nominal())); for (auto const &var_name : systematic::get_variation_names(*this, col)) { syst.set_variation( var_name, this->variation(var_name).weight(col.variation(var_name))); } return syst; } template template , bool>> auto queryosity::varied>::filter( queryosity::column::expression const &expr) -> varied< todo>>> { using varied_type = varied< todo>>>; auto syst = varied_type(this->nominal().filter(expr)); for (auto const &var_name : systematic::get_variation_names(*this)) { syst.set_variation(var_name, this->variation(var_name).filter(expr)); } return syst; } template template , bool>> auto queryosity::varied>::weight( queryosity::column::expression const &expr) -> varied>>> { using varied_type = varied< todo>>>; auto syst = varied_type(this->nominal().weight(expr)); for (auto const &var_name : systematic::get_variation_names(*this)) { syst.set_variation(var_name, this->variation(var_name).weight(expr)); } return syst; } template template , bool>> auto queryosity::varied>::book(Agg &&agg) { return agg.at(*this); } template template , bool>> auto queryosity::varied>::book(Aggs &&...aggs) { return std::make_tuple((aggs.at(*this), ...)); } template template , bool>> auto queryosity::varied>::operator[]( const std::string &var_name) -> queryosity::lazy & { if (!this->has_variation(var_name)) { throw std::out_of_range("variation does not exist"); } return this->variation(var_name); } template template , bool>> auto queryosity::varied>::operator[]( const std::string &var_name) const -> queryosity::lazy const & { if (!this->has_variation(var_name)) { throw std::out_of_range("variation does not exist"); } return this->variation(var_name); } DEFINE_LAZY_VARIED_UNARY_OP(minus, -) DEFINE_LAZY_VARIED_UNARY_OP(logical_not, !) DEFINE_LAZY_VARIED_BINARY_OP(+) DEFINE_LAZY_VARIED_BINARY_OP(-) DEFINE_LAZY_VARIED_BINARY_OP(*) DEFINE_LAZY_VARIED_BINARY_OP(/) DEFINE_LAZY_VARIED_BINARY_OP(<) DEFINE_LAZY_VARIED_BINARY_OP(>) DEFINE_LAZY_VARIED_BINARY_OP(<=) DEFINE_LAZY_VARIED_BINARY_OP(>=) DEFINE_LAZY_VARIED_BINARY_OP(==) DEFINE_LAZY_VARIED_BINARY_OP(&&) DEFINE_LAZY_VARIED_BINARY_OP(||) DEFINE_LAZY_VARIED_BINARY_OP([]) namespace queryosity { /** * @brief Minimal query with an output result. * @details This ABC should be used for actions that do not require any input * columns. */ template class query::aggregation : public node { public: using result_type = T; public: aggregation() = default; virtual ~aggregation() = default; /** * Create and return the result of the query. * If multiple slots are run concurrently in multithreaded mode, the results * are merged into one (see `merge`). * @return The result. */ virtual T result() const = 0; /** * Merge the results from concurrent slots into one representing the full * dataset. * @param[in] results Partial result from each thread. * @return Merged result. */ virtual T merge(std::vector const &results) const = 0; using node::count; /** * Shortcut for `result()`. * @return The result. */ T operator->() const { return this->result(); } protected: }; } // namespace queryosity namespace queryosity { namespace column { template class observable; template class variable; template class view; } /** * @brief Query filled with column value(s) per-entry. * @tparam Out Output result type. * @tparam Ins Input column data types. */ template class query::fillable { public: using vartup_type = std::tuple...>; public: fillable() = default; virtual ~fillable() = default; /** * @brief Perform the counting action for an entry. * @param[in] observables Input column observables. * @param[in] weight The weight value of the booked selection for the passed * entry. * @details This action is performed N times for a passed entry, where N is * the number of `fill()` calls made to its lazy node. */ virtual void fill(column::observable... observables, double weight) = 0; template void enter_columns(column::view const &...cols); protected: std::vector m_fills; }; } // namespace queryosity template template void queryosity::query::fillable::enter_columns( column::view const &...cols) { static_assert(sizeof...(Ins) == sizeof...(Vals), "dimension mis-match between filled variables & columns."); m_fills.emplace_back(cols...); } namespace queryosity { /** * @brief Query filled with column value(s) per-entry. * @tparam Out Output result type. * @tparam Ins Input column data types. */ template class query::definition : public query::aggregation, public query::fillable { public: using vartup_type = std::tuple...>; public: definition() = default; virtual ~definition() = default; /** * @brief Perform the counting action for an entry. * @param[in] observables Input column observables. * @param[in] weight The weight value of the booked selection for the passed * entry. * @details This action is performed N times for a passed entry, where N is * the number of `fill()` calls made to its lazy node. */ virtual void count(double w) final override; }; } // namespace queryosity template void queryosity::query::definition::count(double w) { for (unsigned int ifill = 0; ifill < this->m_fills.size(); ++ifill) { std::apply( [this, w](const column::variable &...obs) { this->fill(obs..., w); }, this->m_fills[ifill]); } } #include namespace queryosity { namespace query { template class series : public queryosity::query::definition(T)> { public: series() = default; ~series() = default; virtual void initialize(unsigned int, unsigned long long, unsigned long long) final override; virtual void fill(column::observable, double) final override; virtual void finalize(unsigned int) final override; virtual std::vector result() const final override; virtual std::vector merge(std::vector> const &results) const final override; protected: std::vector m_result; }; } // namespace query } // namespace queryosity template void queryosity::query::series::initialize(unsigned int, unsigned long long begin, unsigned long long end) { m_result.reserve(end - begin); } template void queryosity::query::series::fill(column::observable x, double) { m_result.push_back(x.value()); } template void queryosity::query::series::finalize(unsigned int) { m_result.resize(m_result.size()); } template std::vector queryosity::query::series::result() const { return m_result; } template std::vector queryosity::query::series::merge(std::vector> const &results) const { std::vector merged; size_t merged_size = 0; for (auto const &result : results) { merged_size += result.size(); } merged.reserve(merged_size); for (auto const &result : results) { merged.insert(merged.end(), result.begin(), result.end()); } return merged; } namespace queryosity { namespace column { /** * @brief Argumnet for column series. * @tparam Col (Varied) lazy column node. * @todo C++20: Use concept to require lazy(::varied)>. */ template struct series { public: using value_type = column::value_t; public: series(Col const &col); ~series() = default; auto make(dataflow &df) const; auto make(lazy &sel) const; auto make(varied> &sel) const -> varied>>; protected: Col m_column; }; } // namespace column } // namespace queryosity template queryosity::column::series::series(Col const &col) : m_column(col){}; template auto queryosity::column::series::make(dataflow &df) const { return df.get(query::output>()).fill(m_column); } template auto queryosity::column::series::make(lazy &sel) const { auto df = sel.m_df; return df->get(query::output>()) .fill(m_column) .at(sel); } template auto queryosity::column::series::make(varied> &sel) const -> varied>> { auto df = sel.nominal().m_df; return df->get(query::output>()) .fill(m_column) .at(sel); } template template queryosity::lazy::lazy(dataflow &df, std::vector const &slots) : dataflow::node(df) { m_slots.clear(); m_slots.reserve(slots.size()); for (auto slot : slots) { m_slots.push_back(static_cast(slot)); } } template template queryosity::lazy::lazy( dataflow &df, std::vector> const &slots) : dataflow::node(df) { m_slots.clear(); m_slots.reserve(slots.size()); for (auto const &slot : slots) { m_slots.push_back(static_cast(slot.get())); } } template template queryosity::lazy::operator lazy() const { return lazy(*this->m_df, this->m_slots); } template std::vector const &queryosity::lazy::get_slots() const { return this->m_slots; } template void queryosity::lazy::set_variation(const std::string &, lazy) { // should never be called throw std::logic_error("cannot set variation to a nominal-only action"); } template auto queryosity::lazy::nominal() -> lazy & { // this is nominal return *this; } template auto queryosity::lazy::variation(const std::string &) -> lazy & { // propagation of variations must occur "transparently" return *this; } template auto queryosity::lazy::nominal() const -> lazy const & { // this is nominal return *this; } template auto queryosity::lazy::variation(const std::string &) const -> lazy const & { // propagation of variations must occur "transparently" return *this; } template std::set queryosity::lazy::get_variation_names() const { // no variations to list return std::set(); } template bool queryosity::lazy::has_variation(const std::string &) const { // always false return false; } template template , bool>> auto queryosity::lazy::to() const -> lazy> { if constexpr (std::is_same_v> || std::is_base_of_v>) { return lazy>(*this->m_df, this->get_slots()); } else { return lazy>( *this->m_df, this->m_df->template _convert(*this).get_slots()); } } template template auto queryosity::lazy::filter(lazy const &col) const { if constexpr (std::is_base_of_v) { return this->m_df->template _apply(*this, col); } else { static_assert(std::is_base_of_v, "filter must be called from a selection"); } } template template auto queryosity::lazy::weight(lazy const &col) const { if constexpr (std::is_base_of_v) { return this->m_df->template _apply(*this, col); } else { static_assert(std::is_base_of_v, "filter must be called from a selection"); } } template template auto queryosity::lazy::filter(Col const &col) const { if constexpr (std::is_base_of_v) { using varied_type = varied>; auto syst = varied_type(this->filter(col.nominal())); for (auto const &var_name : col.get_variation_names()) { syst.set_variation(var_name, this->filter(col.variation(var_name))); } return syst; } else { static_assert(std::is_base_of_v, "filter must be called from a selection"); } } template template auto queryosity::lazy::weight(Col const &col) const { if constexpr (std::is_base_of_v) { using varied_type = varied>; auto syst = varied_type(this->weight(col.nominal())); for (auto const &var_name : col.get_variation_names()) { syst.set_variation(var_name, this->weight(col.variation(var_name))); } return syst; } else { static_assert(std::is_base_of_v, "weight must be called from a selection"); } } template template auto queryosity::lazy::filter( queryosity::column::expression const &expr) const { if constexpr (std::is_base_of_v) { return this->m_df->template _select(*this, expr); } else { static_assert(std::is_base_of_v, "filter must be called from a selection"); } } template template auto queryosity::lazy::weight( queryosity::column::expression const &expr) const { if constexpr (std::is_base_of_v) { return this->m_df->template _select(*this, expr); } else { static_assert(std::is_base_of_v, "filter must be called from a selection"); } } template template auto queryosity::lazy::book(Qry &&qry) const { static_assert(std::is_base_of_v, "book must be called from a selection"); return qry.at(*this); } template template auto queryosity::lazy::book(Qrys &&...qrys) const { static_assert(std::is_base_of_v, "book must be called from a selection"); return std::make_tuple((qrys.at(*this), ...)); } template template , bool>> auto queryosity::lazy::get(queryosity::column::series const &col) -> lazy::value_type>> { return col.make(*this); } template template , bool>> auto queryosity::lazy::get(queryosity::column::series const &col) -> varied::value_type>>> { return col.make(*this); } template template , bool>> auto queryosity::lazy::result() -> decltype(std::declval().result()) { this->m_df->analyze(); this->merge_results(); return this->m_result; } template template , bool> e> void queryosity::lazy::merge_results() { if (this->m_merged) return; auto model = this->get_slot(0); using result_type = decltype(model->result()); const auto nslots = this->size(); if (nslots == 1) { this->m_result = std::move(model->result()); } else { std::vector results; results.reserve(nslots); for (size_t islot = 0; islot < nslots; ++islot) { results.push_back(std::move(this->get_slot(islot)->result())); } this->m_result = std::move(model->merge(results)); } this->m_merged = true; } template queryosity::dataflow::input::loaded(queryosity::dataflow &df, DS &ds) : m_df(&df), m_ds(&ds) {} template template auto queryosity::dataflow::input::read(dataset::column const &col) -> lazy> { return col.template _read(*this); } template template auto queryosity::dataflow::input::read( dataset::column const &...cols) { return std::make_tuple(cols.template _read(*this)...); } template template auto queryosity::dataflow::input::vary( dataset::column const &col, std::map const &vars) { auto nom = this->read(col); varied varied_column(std::move(nom)); for (auto const &var : vars) { varied_column.set_variation(var.first, this->read(dataset::column(var.second))); } return varied_column; } namespace queryosity { class dataflow; template class lazy; namespace column { /** * @ingroup api * @brief Argument to define a column of constant value in the dataflow. * @tparam Val Data type of the constant value. */ template struct constant { public: /** * @brief Argument constructor. * @param[in] val Constant value. */ constant(Val const &val); ~constant() = default; auto _assign(dataflow &df) const -> lazy>; protected: Val m_val; }; } // namespace column } // namespace queryosity template queryosity::column::constant::constant(Val const &val) : m_val(val) {} template auto queryosity::column::constant::_assign(queryosity::dataflow &df) const -> lazy> { return df._assign(this->m_val); } #include #include namespace queryosity { class dataflow; template class lazy; namespace selection { class node; } namespace column { /** * @brief Argument to define a column evaluated out of an expression in the * dataflow. * @tparam Expr Concrete type of C++ function, functor, or lambda. */ template struct expression { public: using function_type = decltype(std::function(std::declval())); using equation_type = equation_t; public: /** * @brief Argument constructor. * @param[in] expr The callable expression. */ expression(Expr expr); ~expression() = default; auto _equate(dataflow &df) const; template auto _select(dataflow &df) const; template auto _select(dataflow &df, lazy const &presel) const; protected: function_type m_expression; }; } // namespace column } // namespace queryosity template queryosity::column::expression::expression(Expr expr) : m_expression(std::move(expr)) {} template auto queryosity::column::expression::_equate( queryosity::dataflow &df) const { return df._equate(this->m_expression); } template template auto queryosity::column::expression::_select( queryosity::dataflow &df) const { return df._select(this->m_expression); } template template auto queryosity::column::expression::_select( queryosity::dataflow &df, lazy const &presel) const { return df._select(presel, this->m_expression); } namespace queryosity { template class lazy; namespace column { template struct nominal { public: using column_type = valued>; public: nominal(lazy const &nom); ~nominal() = default; auto get() const -> lazy>> const &; protected: lazy>> const &m_nom; }; } // namespace systematic } // namespace queryosity template queryosity::column::nominal::nominal(lazy const &nom) : m_nom(nom) {} template auto queryosity::column::nominal::get() const -> lazy>> const & { return m_nom; } #include #include #include namespace queryosity { namespace column { template struct variation { public: template variation(lazy const& var); ~variation() = default; auto get() const -> lazy> const &; protected: lazy> m_var; }; } } // namespace queryosity template template queryosity::column::variation::variation(queryosity::lazy const& var) : m_var(var.template to()) {} template auto queryosity::column::variation::get() const -> lazy> const & { return m_var; } #include #include #include namespace queryosity { class dataflow; namespace query { /** * @ingroup api * @brief Argument to specify a query in the dataflow. * @tparam Qry Concrete implementation of * `queryosity::query::definition`. */ template struct output { public: /** * @brief Argument constructor. * @tparam Args Constructor argument types for @p Qry. * @param args Constructor arguments for @p Qry. */ template output(Args const &...args); ~output() = default; auto make(dataflow &df) const; protected: std::function>(dataflow &)> m_make; }; } // namespace query } // namespace queryosity template template queryosity::query::output::output(Args const &...args) : m_make([args...](dataflow &df) { return df._make(args...); }) {} template auto queryosity::query::output::make(queryosity::dataflow &df) const { return this->m_make(df); } inline queryosity::dataflow::dataflow() : m_processor(multithread::disable()), m_weight(1.0), m_nrows(-1), m_analyzed(false) {} template queryosity::dataflow::dataflow(Kwd &&kwarg) : dataflow() { this->accept_kwarg(std::forward(kwarg)); } template queryosity::dataflow::dataflow(Kwd1 &&kwarg1, Kwd2 &&kwarg2) : dataflow() { static_assert(!std::is_same_v, "each keyword argument must be unique"); this->accept_kwarg(std::forward(kwarg1)); this->accept_kwarg(std::forward(kwarg2)); } template queryosity::dataflow::dataflow(Kwd1 &&kwarg1, Kwd2 &&kwarg2, Kwd3 &&kwarg3) : dataflow() { static_assert(!std::is_same_v, "each keyword argument must be unique"); static_assert(!std::is_same_v, "each keyword argument must be unique"); static_assert(!std::is_same_v, "each keyword argument must be unique"); this->accept_kwarg(std::forward(kwarg1)); this->accept_kwarg(std::forward(kwarg2)); this->accept_kwarg(std::forward(kwarg3)); } template void queryosity::dataflow::accept_kwarg(Kwd &&kwarg) { constexpr bool is_mt_v = std::is_same_v; constexpr bool is_weight_v = std::is_same_v; constexpr bool is_nrows_v = std::is_same_v; if constexpr (is_mt_v) { m_processor = std::forward(kwarg); } else if constexpr (is_weight_v) { m_weight = std::forward(kwarg); } else if constexpr (is_nrows_v) { m_nrows = std::forward(kwarg); } else { static_assert(is_mt_v || is_weight_v || is_nrows_v, "unrecognized keyword argument"); } } template auto queryosity::dataflow::load(queryosity::dataset::input &&in) -> queryosity::dataflow::input { auto ds = in.ds.get(); m_sources.emplace_back(std::move(in.ds)); m_sources.back()->parallelize(m_processor.concurrency()); return dataflow::input(*this, *ds); } template auto queryosity::dataflow::read(queryosity::dataset::input in, queryosity::dataset::column const &col) { auto ds = this->load(std::move(in)); return ds.read(col); } template auto queryosity::dataflow::read( queryosity::dataset::input in, queryosity::dataset::column const &...cols) { auto ds = this->load(std::move(in)); return ds.read(cols...); } template auto queryosity::dataflow::define(column::constant const &cnst) -> lazy> { return cnst._assign(*this); } template auto queryosity::dataflow::define(column::expression const &expr) -> todo>> { return this->_equate(expr); } template auto queryosity::dataflow::define(column::definition const &defn) -> todo> { return this->_define(defn); } template auto queryosity::dataflow::filter(lazy const &col) -> lazy { return this->_apply(col); } template auto queryosity::dataflow::weight(lazy const &col) -> lazy { return this->_apply(col); } template auto queryosity::dataflow::filter(varied const &col) { using varied_type = varied>; varied_type syst(this->filter(col.nominal())); for (auto const &var_name : col.get_variation_names()) { syst.set_variation(var_name, this->filter(col.variation(var_name))); } return syst; } template auto queryosity::dataflow::weight(varied const &col) { using varied_type = varied>; varied_type syst(this->weight(col.nominal())); for (auto const &var_name : col.get_variation_names()) { syst.set_variation(var_name, this->weight(col.variation(var_name))); } return syst; } template auto queryosity::dataflow::filter(column::constant const &cnst) -> lazy { return this->filter(this->define(cnst)); } template auto queryosity::dataflow::weight(column::constant const &cnst) -> lazy { return this->weight(this->define(cnst)); } template auto queryosity::dataflow::filter(column::expression const &expr) -> todo>> { return this->_select(expr); } template auto queryosity::dataflow::weight(column::expression const &expr) -> todo>> { return this->_select(expr); } template auto queryosity::dataflow::_make(Args &&...args) -> todo> { return todo>(*this, ensemble::invoke( [&args...](dataset::player *plyr) { return plyr->template make( std::forward(args)...); }, m_processor.get_slots())); } template auto queryosity::dataflow::get(queryosity::query::output const &qry) -> todo> { return qry.make(*this); } template auto queryosity::dataflow::get(queryosity::column::series const &col) { return col.make(*this); } template auto queryosity::dataflow::get(selection::yield const &sels) { return sels.make(*this); } template auto queryosity::dataflow::_evaluate(todo> const &calc, lazy const &...columns) -> lazy { auto act = ensemble::invoke( [](dataset::player *plyr, column::evaluator const *calc, Cols const *...cols) { return plyr->template evaluate(*calc, *cols...); }, m_processor.get_slots(), calc.get_slots(), columns.get_slots()...); auto lzy = lazy(*this, act); return lzy; } template auto queryosity::dataflow::_apply( todo> const &appl, lazy const &...columns) -> lazy { auto act = ensemble::invoke( [](dataset::player *plyr, selection::applicator const *appl, Cols const *...cols) { return plyr->template apply(*appl, *cols...); }, m_processor.get_slots(), appl.get_slots(), columns.get_slots()...); auto lzy = lazy(*this, act); return lzy; } template auto queryosity::dataflow::_book(todo> const &bkr, lazy const &sel) -> lazy { // new query booked: dataset will need to be analyzed this->reset(); auto act = ensemble::invoke( [](dataset::player *plyr, query::booker *bkr, selection::node const *sel) { return plyr->book(*bkr, *sel); }, m_processor.get_slots(), bkr.get_slots(), sel.get_slots()); auto lzy = lazy(*this, act); return lzy; } template auto queryosity::dataflow::_book(todo> const &bkr, lazy const &...sels) -> std::array, sizeof...(Sels)> { return std::array, sizeof...(Sels)>{this->_book(bkr, sels)...}; } inline void queryosity::dataflow::analyze() { if (m_analyzed) return; m_processor.process(m_sources, m_weight, m_nrows); m_analyzed = true; } inline void queryosity::dataflow::reset() { m_analyzed = false; } template auto queryosity::dataflow::vary(column::constant const &cnst, std::map vars) -> varied>> { auto nom = this->define(cnst); using varied_type = varied>>; varied_type syst(std::move(nom)); for (auto const &var : vars) { this->_vary(syst, var.first, column::constant(var.second)); } return syst; } template auto queryosity::dataflow::vary( column::expression const &expr, std::map::function_type> const &vars) -> varied>>> { auto nom = this->_equate(expr); using varied_type = varied; using function_type = typename column::expression::function_type; varied_type syst(std::move(nom)); for (auto const &var : vars) { this->_vary(syst, var.first, column::expression(var.second)); } return syst; } template auto queryosity::dataflow::vary( column::definition const &defn, std::map> const &vars) -> varied>> { auto nom = this->_define(defn); using varied_type = varied; varied_type syst(std::move(nom)); for (auto const &var : vars) { this->_vary(syst, var.first, var.second); } return syst; } template auto queryosity::dataflow::vary( column::nominal const &nom, std::map>> const &vars) -> varied>>> { using varied_type = varied>>>; auto sys = varied_type(std::move(nom.get())); for (auto const &var : vars) { sys.set_variation(var.first, std::move(var.second.get())); } return sys; } template auto queryosity::dataflow::_read(dataset::reader &ds, const std::string &column_name) -> lazy> { auto act = m_processor.read(ds, column_name); auto lzy = lazy>(*this, act); return lzy; } template auto queryosity::dataflow::_assign(Val const &val) -> lazy> { auto act = ensemble::invoke( [&val](dataset::player *plyr) { return plyr->template assign(val); }, m_processor.get_slots()); auto lzy = lazy>(*this, act); return lzy; } template auto queryosity::dataflow::_cut(lazy const &col) -> lazy { return this->filter(col); } template auto queryosity::dataflow::_cut(varied const &col) -> varied> { return this->filter(col); } template auto queryosity::dataflow::_convert(lazy const &col) -> lazy>> { auto act = ensemble::invoke( [](dataset::player *plyr, Col const *from) { return plyr->template convert(*from); }, m_processor.get_slots(), col.get_slots()); auto lzy = lazy>>(*this, act); return lzy; } template auto queryosity::dataflow::_define(Args &&...args) { return todo>(*this, ensemble::invoke( [&args...](dataset::player *plyr) { return plyr->template define( std::forward(args)...); }, m_processor.get_slots())); } template auto queryosity::dataflow::_define(column::definition const &defn) -> todo> { return defn._define(*this); } template auto queryosity::dataflow::_equate(Fn fn) { return todo>>( *this, ensemble::invoke( [fn](dataset::player *plyr) { return plyr->template equate(fn); }, m_processor.get_slots())); } template auto queryosity::dataflow::_equate(column::expression const &expr) -> todo>> { return expr._equate(*this); } template auto queryosity::dataflow::_select(Fn fn) { return todo>>( *this, ensemble::invoke( [fn](dataset::player *plyr) { return plyr->template select(nullptr, fn); }, m_processor.get_slots())); } template auto queryosity::dataflow::_select(lazy const &prev, Fn fn) { return todo>>( *this, ensemble::invoke( [fn](dataset::player *plyr, selection::node const *prev) { return plyr->template select(prev, fn); }, m_processor.get_slots(), prev.get_slots())); } template auto queryosity::dataflow::_select(column::expression const &expr) -> todo>> { return expr.template _select(*this); } template auto queryosity::dataflow::_select(lazy const &prev, column::expression const &expr) -> todo>> { return expr.template _select(*this, prev); } template auto queryosity::dataflow::_apply(lazy const &dec) -> lazy { auto act = ensemble::invoke( [](dataset::player *plyr, Col *col) { return plyr->template apply(nullptr, *col); }, m_processor.get_slots(), dec.get_slots()); auto lzy = lazy(*this, act); return lzy; } template auto queryosity::dataflow::_apply(lazy const &prev, lazy const &dec) -> lazy { auto act = ensemble::invoke( [](dataset::player *plyr, selection::node const *prev, Col *col) { return plyr->template apply(prev, *col); }, m_processor.get_slots(), prev.get_slots(), dec.get_slots()); auto lzy = lazy(*this, act); return lzy; } template void queryosity::dataflow::_vary(Syst &syst, const std::string &name, column::constant const &cnst) { syst.set_variation(name, this->define(cnst)); } template void queryosity::dataflow::_vary(Syst &syst, const std::string &name, column::expression const &expr) { syst.set_variation(name, this->_equate(expr)); } template void queryosity::dataflow::_vary(Syst &syst, const std::string &name, column::definition const &defn) { syst.set_variation(name, this->_define(defn)); } inline queryosity::dataflow::node::node(dataflow &df) : m_df(&df) {} template auto queryosity::dataflow::node::invoke(Fn fn, Nodes const &...nodes) -> std::enable_if_t< !std::is_void_v< std::invoke_result_t>, std::vector< std::invoke_result_t>> { return ensemble::invoke(fn, nodes.get_slots()...); } template auto queryosity::dataflow::node::invoke(Fn fn, Nodes const &...nodes) -> std::enable_if_t>, void> { ensemble::invoke(fn, nodes.get_slots()...); } namespace queryosity { /** * @ingroup api * @brief Complete the instantiation of a lazy action. * @details A todo node is an intermediate state between the dataflow graph and * a lazy node, when additional methods must be chained in order to instantiate * the action. * @tparam Helper Helper class to instantiate the lazy action. */ template class todo : public dataflow::node, public ensemble::slotted, public systematic::resolver> { public: todo(dataflow &df, std::vector> bkr); virtual ~todo() = default; todo(todo &&) = default; todo &operator=(todo &&) = default; virtual std::vector const &get_slots() const final override; virtual void set_variation(const std::string &var_name, todo var) final override; virtual todo &nominal() final override; virtual todo &variation(const std::string &var_name) final override; virtual todo const &nominal() const final override; virtual todo const & variation(const std::string &var_name) const final override; virtual bool has_variation(const std::string &var_name) const final override; virtual std::set get_variation_names() const final override; /** * @brief Evaluate the column definition with input columns. * @param[in] columns Input columns. * @param[in][out] Evaluated column. */ template < typename... Nodes, typename V = Helper, std::enable_if_t, bool> = false> auto evaluate(Nodes &&...columns) const -> decltype(std::declval>()._evaluate( std::forward(columns)...)) { return this->_evaluate(std::forward(columns)...); } /** * @brief Apply the selection with input columns. * @param[in] columns Input columns. * @param[in][out] Applied selection. */ template < typename... Nodes, typename V = Helper, std::enable_if_t, bool> = false> auto apply(Nodes &&...columns) const -> decltype(std::declval>()._apply( std::forward(columns)...)) { return this->template _apply(std::forward(columns)...); } /** * @brief Fill query with input columns per-entry. * @param[in] columns Input columns. * @returns Updated query plan filled with input columns. */ template >, bool> = false> auto fill(Nodes &&...columns) const -> decltype(std::declval>()._fill(std::declval()...)) { return this->_fill(std::forward(columns)...); } /** * @brief Book a query at a selection. * @param[in] sel Selection node at which query is counted/filled. * @return The query booked at the selection. */ template auto at(Node &&selection) const { return this->_book(std::forward(selection)); } /** * @brief Book a query at multiple selections. * @tparam Sels... Selections. * @param[in] sels... selection nodes. * @return `std::tuple` of queries booked at each selection. */ template auto at(Sels &&...sels) const { static_assert(query::is_bookable_v, "not bookable"); return this->_book(std::forward(sels)...); } /** * @brief Shorthand for `evaluate()`. * @tparam Args... Input column types. * @param[in] columns... Input columns. * @return Evaluated column. */ template auto operator()(Args &&...columns) const { if constexpr (column::is_evaluatable_v) { return this->evaluate(std::forward(columns)...); } else if constexpr (selection::is_applicable_v) { return this->apply(std::forward(columns)...); } else if constexpr (query::is_bookable_v) { return this->fill(std::forward(columns)...); } } protected: template && queryosity::has_no_variation_v, bool> = false> auto _evaluate(Nodes const &...columns) const -> lazy> { return this->m_df->_evaluate(*this, columns...); } template && queryosity::has_variation_v, bool> = false> auto _evaluate(Nodes const &...columns) const -> varied>> { using varied_type = varied>>; auto nom = this->m_df->_evaluate(*this, columns.nominal()...); auto sys = varied_type(std::move(nom)); for (auto const &var_name : systematic::get_variation_names(columns...)) { auto var = this->m_df->_evaluate(*this, columns.variation(var_name)...); sys.set_variation(var_name, std::move(var)); } return sys; } template && queryosity::has_no_variation_v, bool> = false> auto _apply(Nodes const &...columns) const -> lazy { using selection_type = typename V::selection_type; return this->m_df->template _apply(*this, columns...); } template && queryosity::has_variation_v, bool> = false> auto _apply(Nodes const &...columns) const -> varied> { using selection_type = typename V::selection_type; using varied_type = varied>; auto nom = this->m_df->template _apply( *this, columns.nominal()...); auto sys = varied_type(nom); for (auto const &var_name : systematic::get_variation_names(columns...)) { auto var = this->m_df->template _apply( *this, columns.variation(var_name)...); sys.set_variation(var_name, var); } return sys; } template && queryosity::is_nominal_v, bool> = false> auto _book(Node const &sel) const -> lazy> { return this->m_df->_book(*this, sel); } template && queryosity::is_varied_v, bool> = false> auto _book(Node const &sel) const -> varied>> { using varied_type = varied>>; auto sys = varied_type(this->m_df->_book(*this, sel.nominal())); for (auto const &var_name : systematic::get_variation_names(sel)) { sys.set_variation(var_name, this->m_df->_book(*this, sel.variation(var_name))); } return sys; } template && queryosity::has_no_variation_v, bool> = false> auto _book(Nodes const &...sels) const -> std::array>, sizeof...(Nodes)> { return std::array>, sizeof...(Nodes)>{ this->m_df->_book(*this, sels)...}; } template && has_variation_v, bool> = false> auto _book(Nodes const &...sels) const -> std::array>>, sizeof...(Nodes)> { using varied_type = varied>>; using array_of_varied_type = std::array; auto var_names = systematic::get_variation_names(sels...); auto _book_varied = [var_names, this](systematic::resolver> const &sel) { auto sys = varied_type(this->m_df->_book(*this, sel.nominal())); for (auto const &var_name : var_names) { sys.set_variation( var_name, this->m_df->_book(*this, sel.variation(var_name))); } return sys; }; return array_of_varied_type{_book_varied(sels)...}; } template > && queryosity::has_no_variation_v, bool> = false> auto _fill(Nodes const &...columns) const -> todo { return todo(*this->m_df, ensemble::invoke( [](V *fillable, typename Nodes::action_type *...cols) { return fillable->add_columns(*cols...); }, this->get_slots(), columns.get_slots()...)); } template > && has_variation_v, bool> = false> auto _fill(Nodes const &...columns) const -> varied> { using varied_type = varied>; auto sys = varied_type(std::move(this->_fill(columns.nominal()...))); for (auto const &var_name : systematic::get_variation_names(columns...)) { sys.set_variation( var_name, std::move(this->_fill(columns.variation(var_name)...))); } return sys; } protected: std::vector> m_slots; std::vector m_ptrs; }; } // namespace queryosity template queryosity::todo::todo(queryosity::dataflow &df, std::vector> bkr) : dataflow::node(df), m_slots(std::move(bkr)) { m_ptrs.reserve(m_slots.size()); for (auto const &slot : m_slots) { m_ptrs.push_back(slot.get()); } } template std::vector const &queryosity::todo::get_slots() const { return m_ptrs; } template void queryosity::todo::set_variation(const std::string &, todo) { // should never be called throw std::logic_error("cannot set variation to a nominal-only action"); } template auto queryosity::todo::nominal() -> todo & { // this is nominal return *this; } template auto queryosity::todo::variation(const std::string &) -> todo & { // propagation of variations must occur "transparently" return *this; } template auto queryosity::todo::nominal() const -> todo const & { // this is nominal return *this; } template auto queryosity::todo::variation(const std::string &) const -> todo const & { // propagation of variations must occur "transparently" return *this; } template std::set queryosity::todo::get_variation_names() const { // no variations to list return std::set(); } template bool queryosity::todo::has_variation(const std::string &) const { // always false return false; } namespace queryosity { class dataflow; /** * @ingroup abc * @brief Column with user-defined return value type and evaluation * dataset. * @tparam Out Output data type. * @tparam Ins Input column data type(s). */ template class column::definition : public column::calculation { public: using vartuple_type = std::tuple...>; using obstuple_type = std::tuple...>; public: definition() = default; virtual ~definition() = default; public: virtual Out calculate() const final override; /** * @brief Compute the quantity of interest for the entry * @note Columns observables are not computed until `value()` is * called. * @param[in] args Input column observables. */ virtual Out evaluate(observable... args) const = 0; template void set_arguments(const view &...args); protected: vartuple_type m_arguments; }; /** * @ingroup api * @brief Argument to define a custom column in the dataflow. * @tparam Def Concrete implementation of * `queryosity::column::definition` */ template class column::definition { public: /** * @brief Argument constructor. * @param[in] args Constructor arguments of @p Def. */ template definition(Args const &...args); auto _define(dataflow &df) const; protected: std::function>(dataflow &)> m_define; }; } // namespace queryosity template template void queryosity::column::definition::set_arguments( view const &...args) { static_assert(sizeof...(Ins) == sizeof...(Args)); m_arguments = std::make_tuple(std::invoke( [](const view &args) -> variable { return variable(args); }, args)...); } template Out queryosity::column::definition::calculate() const { return std::apply( [this](const variable &...args) { return this->evaluate(args...); }, m_arguments); } template template queryosity::column::definition::definition(Args const &...args) { m_define = [args...](dataflow &df) { return df._define(args...); }; } template auto queryosity::column::definition::_define(dataflow &df) const { return this->m_define(df); } #include namespace queryosity { namespace selection { /** * @brief Yield (sum of weights and squared error) at a selection. */ struct count_t { unsigned long long entries; double value; double error; }; class counter : public query::aggregation { public: counter() = default; virtual ~counter() = default; virtual void count(double w) final override; virtual count_t result() const final override; virtual void finalize(unsigned int) final override; virtual count_t merge(std::vector const &results) const final override; protected: count_t m_cnt; }; /** * @brief Argumnet for column yield. * @tparam Sel (Varied) lazy column node. * @todo C++20: Use concept to require lazy(::varied)>. */ template struct yield { public: yield(Sels const &...sels); ~yield() = default; auto make(dataflow &df) const; protected: std::tuple m_selections; }; } // namespace selection } // namespace queryosity inline void queryosity::selection::counter::count(double w) { m_cnt.entries++; m_cnt.value += w; m_cnt.error += w * w; } inline void queryosity::selection::counter::finalize(unsigned int) { m_cnt.error = std::sqrt(m_cnt.error); } inline queryosity::selection::count_t queryosity::selection::counter::result() const { return m_cnt; } inline queryosity::selection::count_t queryosity::selection::counter::merge(std::vector const& cnts) const { count_t sum; for (auto const &cnt : cnts) { sum.entries += cnt.entries; sum.value += cnt.value; sum.error += cnt.error * cnt.error; } sum.error = std::sqrt(sum.error); return sum; } template queryosity::selection::yield::yield(Sels const &...sels) : m_selections(sels...) {} template auto queryosity::selection::yield::make(dataflow &df) const { return std::apply( [&df](Sels const &...sels) { return df.get(query::output()).at(sels...); }, m_selections); } namespace queryosity { /** * @ingroup api * @brief Varied version of a todo item. * @details A todo varied item is functionally equivalent to a @p todo * node with each method being propagated to independent todo nodes * corresponding to nominal and systematic variations. */ template class varied> : public dataflow::node, systematic::resolver> { public: template friend class lazy; template friend class varied; public: varied(todo &&nom); ~varied() = default; varied(varied &&) = default; varied &operator=(varied &&) = default; virtual void set_variation(const std::string &var_name, todo var) final override; virtual todo &nominal() final override; virtual todo &variation(const std::string &var_name) final override; virtual todo const &nominal() const final override; virtual todo const & variation(const std::string &var_name) const final override; virtual bool has_variation(const std::string &var_name) const final override; virtual std::set get_variation_names() const final override; public: template < typename... Cols, typename V = Helper, std::enable_if_t, bool> = false> auto evaluate(Cols &&...cols) -> varied< decltype(this->nominal().evaluate(std::forward(cols.nominal)...))>; template < typename... Cols, typename V = Helper, std::enable_if_t, bool> = false> auto apply(Cols &&...cols) -> varied>>; /** * @brief Fill the query with input columns. * @param[in] columns... Input columns to fill the query with. * @return A new todo query node with input columns filled. */ template >, bool> = false> auto fill(Nodes const &...columns) -> varied; /** * @brief Book the query at a selection. * @param[in] selection Lazy selection to book query at. * @return Lazy query booked at selection. */ template , bool> = false> auto at(Node const &selection) -> varied>>; /** * @brief Book the query at multiple selections. * @param[in] selection Lazy selection to book queries at. * @return Delayed query containing booked lazy queries. */ template , bool> = false> auto at(Nodes const &...selections) -> std::array>>, sizeof...(Nodes)>; template auto operator()(Cols &&...cols) -> varied>().operator()( std::forward(cols).nominal()...))>; protected: todo m_nominal; std::unordered_map> m_variation_map; std::set m_variation_names; }; } // namespace queryosity template queryosity::varied>::varied( queryosity::todo &&nom) : dataflow::node(*nom.m_df), m_nominal(std::move(nom)) {} template void queryosity::varied>::set_variation( const std::string &var_name, queryosity::todo var) { m_variation_map.insert(std::move(std::make_pair(var_name, std::move(var)))); m_variation_names.insert(var_name); } template auto queryosity::varied>::nominal() -> todo & { return m_nominal; } template auto queryosity::varied>::nominal() const -> todo const & { return m_nominal; } template auto queryosity::varied>::variation( const std::string &var_name) -> todo & { return (this->has_variation(var_name) ? m_variation_map.at(var_name) : m_nominal); } template auto queryosity::varied>::variation( const std::string &var_name) const -> todo const & { return (this->has_variation(var_name) ? m_variation_map.at(var_name) : m_nominal); } template bool queryosity::varied>::has_variation( const std::string &var_name) const { return m_variation_map.find(var_name) != m_variation_map.end(); } template std::set queryosity::varied>::get_variation_names() const { return m_variation_names; } template template , bool>> auto queryosity::varied>::evaluate(Cols &&...cols) -> variednominal().evaluate( std::forward(cols.nominal)...))> { using varied_type = variednominal().evaluate( std::forward(cols.nominal)...))>; auto syst = varied_type( this->nominal().evaluate(std::forward(cols).nominal()...)); for (auto const &var_name : systematic::get_variation_names(*this, std::forward(cols)...)) { syst.set_variation(var_name, variation(var_name).evaluate( std::forward(cols).variation(var_name)...)); } return syst; } template template , bool>> auto queryosity::varied>::apply(Cols &&...cols) -> varied>> { using varied_type = varied>>; auto syst = varied_type(this->nominal().apply(std::forward(cols).nominal()...)); for (auto const &var_name : systematic::get_variation_names(*this, std::forward(cols)...)) { syst.set_variation(var_name, variation(var_name).apply( std::forward(cols).variation(var_name)...)); } return syst; } template template >, bool>> auto queryosity::varied>::fill(Nodes const &...columns) -> varied { auto syst = varied(std::move(this->nominal().fill(columns.nominal()...))); for (auto const &var_name : systematic::get_variation_names(*this, columns...)) { syst.set_variation(var_name, std::move(variation(var_name).fill( columns.variation(var_name)...))); } return syst; } template template , bool>> auto queryosity::varied>::at(Node const &selection) -> varied>> { using varied_type = varied>>; auto syst = varied_type(this->nominal().at(selection.nominal())); for (auto const &var_name : systematic::get_variation_names(*this, selection)) { syst.set_variation( var_name, this->variation(var_name).at(selection.variation(var_name))); } return syst; } template template , bool>> auto queryosity::varied>::at( Nodes const &...selections) -> std::array>>, sizeof...(Nodes)> { // variations using varied_type = varied>>; using array_of_varied_type = std::array>>, sizeof...(Nodes)>; auto var_names = systematic::get_variation_names(*this, selections...); auto _book_varied = [var_names, this](systematic::resolver> const &sel) { auto syst = varied_type(this->nominal().at(sel.nominal())); for (auto const &var_name : var_names) { syst.set_variation( var_name, this->variation(var_name).at(sel.variation(var_name))); } return syst; }; return array_of_varied_type{_book_varied(selections)...}; } template template auto queryosity::varied>::operator()(Cols &&...cols) -> varied>().operator()( std::forward(cols).nominal()...))> { using varied_type = varied>().operator()( std::forward(cols).nominal()...))>; auto syst = varied_type( this->nominal().operator()(std::forward(cols).nominal()...)); for (auto const &var_name : systematic::get_variation_names(*this, std::forward(cols)...)) { syst.set_variation(var_name, variation(var_name).operator()( std::forward(cols).variation(var_name)...)); } return syst; } namespace qty = queryosity;