/* This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; version 2 of the License. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA © 2020 Thales group © 2022 Institut Pasteur Authors: Johann Dreo */ #ifndef _eoForge_H_ #define _eoForge_H_ #include #include #include #include // In case you want to debug arguments captured in tuples: // template // struct tuple_printer { // // static void print(std::ostream& out, const Type& value) { // out << std::get(value) << ", "; // tuple_printer::print(out, value); // } // }; // // template // struct tuple_printer { // // static void print(std::ostream& out, const Type& value) { // out << std::get(value); // } // // }; // // template // std::ostream& operator<<(std::ostream& out, const std::tuple& value) { // out << "("; // tuple_printer, 0, sizeof...(Types) - 1>::print(out, value); // out << ")"; // return out; // } /** * @defgroup Foundry * * Tools for automatic algorithms assembling, selection and search. */ /** Interface for a "Forge": a class that can defer instantiation of EO's operator. * * This interface only declares an `instantiate` method, * in order to be able to make containers of factories (@see eoForgeOperator). * * @ingroup Core * @ingroup Foundry */ template class eoForgeInterface { public: virtual Itf& instantiate(bool no_cache = true) = 0; virtual std::shared_ptr instantiate_ptr(bool no_cache = true) = 0; virtual ~eoForgeInterface() {} }; /** This "Forge" can defer the instantiation of an EO's operator. * * It allows to decouple the constructor's parameters setup from its actual call. * You can declare a parametrized operator at a given time, * then actually instantiate it (with the given interface) at another time. * * This allows for creating containers of pre-parametrized operators (@see eoForgeVector). * * @warning When passing a reference (as it is often the case within ParadisEO), * it is MANDATORY to wrap it in `std::ref`, or else it will default to use copy. * This is is a source of bug which your compiler will to detect and that would * disable any link between operators. * * @code eoForgeOperator,eoRankMuSelect> forge(mu); // ^ desired ^ to-be-instantiated ^ operator's // interface operator parameters // Actual instantiation: eoSelect& select = forge.instantiate(); * @endcode * * @warning You may want to enable instantiation cache to grab some performances. * The default is set to disable the cache, because its use with operators * which hold a state will lead to unwanted behaviour. * * @ingroup Foundry */ template class eoForgeOperator : public eoForgeInterface { public: // Use an additional template to avoid redundant copies of decayed Args variadic. template eoForgeOperator(Args2... args) : _args(std::forward(args)...), _instantiated_ptr(nullptr), _instantiated(nullptr) { } /** instantiate the managed operator class. * * That is call its constructor with the set up arguments. * * @warning Do not enable cache with operators which hold a state. * * @param no_cache If false, will enable caching previous instances. */ Itf& instantiate(bool no_cache = true) override { if(no_cache or _instantiated == nullptr) { if(_instantiated) { delete _instantiated; } _instantiated = op_constructor(_args); } return *_instantiated; } std::shared_ptr instantiate_ptr(bool no_cache = true) override { if(no_cache or _instantiated == nullptr) { // if(_instantiated) { // delete _instantiated; // } _instantiated_ptr = op_constructor_ptr(_args); // _instantiated = op_constructor(_args); } return _instantiated_ptr; } virtual ~eoForgeOperator() override { delete _instantiated; } protected: std::tuple _args; private: /** Metaprogramming machinery which deals with arguments lists @{ */ template Op* op_constructor(T& args) { // FIXME double-check that the copy-constructor is a good idea to make_from_tuple with dynamic storage duration. return new Op(std::make_from_tuple(args)); } template std::shared_ptr op_constructor_ptr(T& args) { return std::make_shared( std::make_from_tuple(args) ); } /** @} */ protected: std::shared_ptr _instantiated_ptr; Itf* _instantiated; }; /** Partial specialization for constructors without any argument. */ template class eoForgeOperator : public eoForgeInterface { public: eoForgeOperator() : _instantiated_ptr(nullptr), _instantiated(nullptr) { } Itf& instantiate( bool no_cache = true ) override { if(no_cache or _instantiated == nullptr) { if(_instantiated) { delete _instantiated; } _instantiated = new Op; } return *_instantiated; } std::shared_ptr instantiate_ptr( bool no_cache = true ) override { if(no_cache or _instantiated == nullptr) { _instantiated_ptr = std::shared_ptr(); } return _instantiated_ptr; } virtual ~eoForgeOperator() override { delete _instantiated; } protected: std::shared_ptr _instantiated_ptr; Itf* _instantiated; }; /** A vector holding an operator (with deferred instantiation) at a given index. * * @note You can actually store several instances of the same class, * with different parametrization (or not). * * @warning When passing a reference (as it is often the case within ParadisEO), * it is MANDATORY to wrap it in `std::ref`, or else it will default to use copy. * This is is a source of bug which your compiler will fail to detect and that would * disable any link between operators. * * @warning You may want to enable instantiation cache to grab some performances. * The default is set to disable the cache, because its use with operators * which hold a state will lead to unwanted behaviour. * * @code eoForgeVector> factories(false); // Capture constructor's parameters and defer instantiation. factories.add>(1); factories.setup>(0, 5); // Edit // Actually instantiate. eoSelect& op = factories.instantiate(0); // Call. op(); * @endcode * * @ingroup Foundry */ template class eoForgeVector : public std::vector*> { public: using Interface = Itf; /** Default constructor do not cache instantiations. * * @warning * You most probably want to disable caching for operators that hold a state. * If you enable the cache, the last used instantiation will be used, * at its last state. * For example, continuators should most probably not be cached, * as they very often hold a state in the form of a counter. * At the end of a search, the continuator will be in the end state, * and thus always ask for a stop. * Reusing an instance in this state will de facto disable further searches. * * @param always_reinstantiate If false, will enable cache for the forges in this container. */ eoForgeVector( bool always_reinstantiate = true ) : _no_cache(always_reinstantiate) { } /** instantiate the operator managed at the given index. */ Itf& instantiate_from(double index) { double frac_part, int_part; frac_part = std::modf(index, &int_part); if(frac_part != 0) { eo::log << eo::errors << "there is a fractional part in the given index (" << index << ")" << std::endl; assert(frac_part != 0); } return instantiate(index); } std::shared_ptr instantiate_ptr_from(double index) { double frac_part, int_part; frac_part = std::modf(index, &int_part); if(frac_part != 0) { eo::log << eo::errors << "there is a fractional part in the given index (" << index << ")" << std::endl; assert(frac_part != 0); } return instantiate_ptr(index); } /** instantiate the operator managed at the given index. */ Itf& instantiate(size_t index) { return this->at(static_cast(index))->instantiate(_no_cache); } std::shared_ptr instantiate_ptr(size_t index) { return this->at(static_cast(index))->instantiate_ptr(_no_cache); } /** Add an operator to the list. * * @warning When passing a reference (as it is often the case within ParadisEO), * it is MANDATORY to wrap it in `std::ref`, or else it will default to use copy. * This is is a source of bug which your compiler will to detect and that would * disable any link between operators. * */ template void add(Args... args) { // We decay all args to ensure storing everything by value within the forge. // The references should thus be wrapped in a std::ref. auto pfo = new eoForgeOperator...>( std::forward(args)...); this->push_back(pfo); } /** Specialization for operators with empty constructors. */ template void add() { eoForgeInterface* pfo = new eoForgeOperator; this->push_back(pfo); } /** Change the set up arguments to the constructor. * * @warning When passing a reference (as it is often the case within ParadisEO), * it is MANDATORY to wrap it in `std::ref`, or else it will default to use copy. * This is is a source of bug which your compiler will to detect and that would * disable any link between operators. * * @warning The operator at `index` should have been added with eoForgeVector::add already.. */ template void setup(size_t index, Args... args) { assert(index < this->size()); delete this->at(index); // Silent on nullptr. auto pfo = new eoForgeOperator...>( std::forward(args)...); this->at(index) = pfo; } /** Specialization for empty constructors. */ template void setup(size_t index) { assert(index < this->size()); delete this->at(index); auto pfo = new eoForgeOperator; this->at(index) = pfo; } virtual ~eoForgeVector() { for(auto p : *this) { delete p; } } protected: bool _no_cache; }; /** A map holding an operator (with deferred instantiation) at a given name. * * @note You can actually store several instances of the same class, * with different parametrization (or not). * * @warning When passing a reference (as it is often the case within ParadisEO), * it is MANDATORY to wrap it in `std::ref`, or else it will default to use copy. * This is is a source of bug which your compiler will fail to detect and that would * disable any link between operators. * * @warning You may want to enable instantiation cache to grab some performances. * The default is set to disable the cache, because its use with operators * which hold a state will lead to unwanted behaviour. * * @code eoForgeMap> factories(false); // Capture constructor's parameters and defer instantiation. factories.add>(1); factories.setup>(0, 5); // Edit // Actually instantiate. eoSelect& op = factories.instantiate(0); // Call. op(); * @endcode * * @ingroup Foundry */ template class eoForgeMap : public std::map*> { public: using Interface = Itf; /** Default constructor do not cache instantiations. * * @warning * You most probably want to disable caching for operators that hold a state. * If you enable the cache, the last used instantiation will be used, * at its last state. * For example, continuators should most probably not be cached, * as they very often hold a state in the form of a counter. * At the end of a search, the continuator will be in the end state, * and thus always ask for a stop. * Reusing an instance in this state will de facto disable further searches. * * @param always_reinstantiate If false, will enable cache for the forges in this container. */ eoForgeMap( bool always_reinstantiate = true ) : _no_cache(always_reinstantiate) { } /** instantiate the operator managed at the given name. */ Itf& instantiate(const std::string& name) { return this->at(name)->instantiate(_no_cache); } /** Add an operator to the list. * * @warning When passing a reference (as it is often the case within ParadisEO), * it is MANDATORY to wrap it in `std::ref`, or else it will default to use copy. * This is is a source of bug which your compiler will to detect and that would * disable any link between operators. * */ template void add(const std::string& name, Args... args) { // We decay all args to ensure storing everything by value within the forge. // The references should thus be wrapped in a std::ref. auto pfo = new eoForgeOperator...>( std::forward(args)...); this->insert({name, pfo}); } /** Specialization for operators with empty constructors. */ template void add(const std::string& name) { eoForgeInterface* pfo = new eoForgeOperator; this->insert({name, pfo}); } /** Change the set up arguments to the constructor. * * @warning When passing a reference (as it is often the case within ParadisEO), * it is MANDATORY to wrap it in `std::ref`, or else it will default to use copy. * This is is a source of bug which your compiler will to detect and that would * disable any link between operators. * * @warning The operator at `name` should have been added with eoForgeMap::add already.. */ template void setup(const std::string& name, Args... args) { delete this->at(name); // Silent on nullptr. auto pfo = new eoForgeOperator...>( std::forward(args)...); this->emplace({name, pfo}); } /** Specialization for empty constructors. */ template void setup(const std::string& name) { delete this->at(name); auto pfo = new eoForgeOperator; this->emplace({name, pfo}); } virtual ~eoForgeMap() { for(auto kv : *this) { delete kv.second; } } protected: bool _no_cache; }; /** A range holding a parameter value at a given index. * * This is essential a scalar numerical parameter, with bounds check * and an interface similar to an eoForgeVector. * * @note Contrary to eoForgeVector, this does not store a set of possible values. * * @code eoForgeScalar factories(0.0, 1.0); // Actually instantiate. double param = factories.instantiate(0.5); * @endcode * * @ingroup Foundry */ template class eoForgeScalar { public: using Interface = Itf; /** Constructor * * @param min Minimum possible value. * @param may Maximum possible value. */ eoForgeScalar(Itf min, Itf max) : _min(min), _max(max) { } /** Just return the same value, without managing any instantiation. * * Actually checks if value is in range. */ Itf& instantiate(double value) { this->_value = value; if(not (_min <= value and value <= _max) ) { eo::log << eo::errors << "ERROR: the given value is out of range, I'll cap it." << std::endl; assert(_min <= value and value <= _max); if(value < _min) { this->_value = _min; return this->_value; } if(value > _max) { this->_value = _max; return this->_value; } } return this->_value; } Itf min() const { return _min; } Itf max() const { return _max; } /** Set the minimum possible value. */ void min(Itf min) { assert(_min <= _max); _min = min; } /** Set the maximum possible value. */ void max(Itf max) { assert(_max >= _min); _max = max; } /** Set the possible range of values. */ void setup(Itf min, Itf max) { _min = min; _max = max; assert(_min <= _max); } // Nothing else, as it would not make sense. protected: Itf _value; Itf _min; Itf _max; }; #endif // _eoForge_H_