- Move the instantiate(double) interfaces of eoForgeVector as instantiate_from. - Adds two separated sets members for instantiation. BREAKING CHANGE
598 lines
20 KiB
C++
598 lines
20 KiB
C++
|
|
/*
|
|
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 <johann@dreo.fr>
|
|
*/
|
|
|
|
#ifndef _eoForge_H_
|
|
#define _eoForge_H_
|
|
|
|
#include <string>
|
|
#include <tuple>
|
|
#include <utility>
|
|
#include <memory>
|
|
|
|
// In case you want to debug arguments captured in tuples:
|
|
// template<typename Type, unsigned N, unsigned Last>
|
|
// struct tuple_printer {
|
|
//
|
|
// static void print(std::ostream& out, const Type& value) {
|
|
// out << std::get<N>(value) << ", ";
|
|
// tuple_printer<Type, N + 1, Last>::print(out, value);
|
|
// }
|
|
// };
|
|
//
|
|
// template<typename Type, unsigned N>
|
|
// struct tuple_printer<Type, N, N> {
|
|
//
|
|
// static void print(std::ostream& out, const Type& value) {
|
|
// out << std::get<N>(value);
|
|
// }
|
|
//
|
|
// };
|
|
//
|
|
// template<typename... Types>
|
|
// std::ostream& operator<<(std::ostream& out, const std::tuple<Types...>& value) {
|
|
// out << "(";
|
|
// tuple_printer<std::tuple<Types...>, 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 Itf>
|
|
class eoForgeInterface
|
|
{
|
|
public:
|
|
virtual Itf& instantiate(bool no_cache = true) = 0;
|
|
virtual std::shared_ptr<Itf> 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<eoselect<EOT>,eoRankMuSelect<EOT>> forge(mu);
|
|
// ^ desired ^ to-be-instantiated ^ operator's
|
|
// interface operator parameters
|
|
// Actual instantiation:
|
|
eoSelect<EOT>& 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 Itf, class Op, typename... Args>
|
|
class eoForgeOperator : public eoForgeInterface<Itf>
|
|
{
|
|
public:
|
|
// Use an additional template to avoid redundant copies of decayed Args variadic.
|
|
template<class ...Args2>
|
|
eoForgeOperator(Args2... args) :
|
|
_args(std::forward<Args2>(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<Itf> 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...> _args;
|
|
|
|
private:
|
|
/** Metaprogramming machinery which deals with arguments lists @{ */
|
|
template<class T>
|
|
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<Op>(args));
|
|
}
|
|
|
|
template<class T>
|
|
std::shared_ptr<Op> op_constructor_ptr(T& args)
|
|
{
|
|
return std::make_shared<Op>( std::make_from_tuple<Op>(args) );
|
|
}
|
|
/** @} */
|
|
|
|
protected:
|
|
std::shared_ptr<Itf> _instantiated_ptr;
|
|
Itf* _instantiated;
|
|
};
|
|
|
|
/** Partial specialization for constructors without any argument.
|
|
*/
|
|
template<class Itf, class Op>
|
|
class eoForgeOperator<Itf,Op> : public eoForgeInterface<Itf>
|
|
{
|
|
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<Itf> instantiate_ptr( bool no_cache = true ) override
|
|
{
|
|
if(no_cache or _instantiated == nullptr) {
|
|
_instantiated_ptr = std::shared_ptr<Op>();
|
|
}
|
|
return _instantiated_ptr;
|
|
}
|
|
|
|
virtual ~eoForgeOperator() override
|
|
{
|
|
delete _instantiated;
|
|
}
|
|
|
|
protected:
|
|
std::shared_ptr<Itf> _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<eoSelect<EOT>> factories(false);
|
|
|
|
// Capture constructor's parameters and defer instantiation.
|
|
factories.add<eoRankMuSelect<EOT>>(1);
|
|
factories.setup<eoRankMuSelect<EOT>>(0, 5); // Edit
|
|
|
|
// Actually instantiate.
|
|
eoSelect<EOT>& op = factories.instantiate(0);
|
|
|
|
// Call.
|
|
op();
|
|
* @endcode
|
|
*
|
|
* @ingroup Foundry
|
|
*/
|
|
template<class Itf, typename Enable = void>
|
|
class eoForgeVector : public std::vector<eoForgeInterface<Itf>*>
|
|
{
|
|
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<Itf> 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<size_t>(index))->instantiate(_no_cache);
|
|
}
|
|
|
|
std::shared_ptr<Itf> instantiate_ptr(size_t index)
|
|
{
|
|
return this->at(static_cast<size_t>(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<class Op, typename... Args>
|
|
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<Itf,Op,std::decay_t<Args>...>(
|
|
std::forward<Args>(args)...);
|
|
this->push_back(pfo);
|
|
}
|
|
|
|
/** Specialization for operators with empty constructors.
|
|
*/
|
|
template<class Op>
|
|
void add()
|
|
{
|
|
eoForgeInterface<Itf>* pfo = new eoForgeOperator<Itf,Op>;
|
|
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<class Op, typename... Args>
|
|
void setup(size_t index, Args... args)
|
|
{
|
|
assert(index < this->size());
|
|
delete this->at(index); // Silent on nullptr.
|
|
auto pfo = new eoForgeOperator<Itf,Op,std::decay_t<Args>...>(
|
|
std::forward<Args>(args)...);
|
|
this->at(index) = pfo;
|
|
}
|
|
|
|
/** Specialization for empty constructors.
|
|
*/
|
|
template<class Op>
|
|
void setup(size_t index)
|
|
{
|
|
assert(index < this->size());
|
|
delete this->at(index);
|
|
auto pfo = new eoForgeOperator<Itf,Op>;
|
|
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<eoSelect<EOT>> factories(false);
|
|
|
|
// Capture constructor's parameters and defer instantiation.
|
|
factories.add<eoRankMuSelect<EOT>>(1);
|
|
factories.setup<eoRankMuSelect<EOT>>(0, 5); // Edit
|
|
|
|
// Actually instantiate.
|
|
eoSelect<EOT>& op = factories.instantiate(0);
|
|
|
|
// Call.
|
|
op();
|
|
* @endcode
|
|
*
|
|
* @ingroup Foundry
|
|
*/
|
|
template<class Itf, typename Enable = void>
|
|
class eoForgeMap : public std::map<std::string,eoForgeInterface<Itf>*>
|
|
{
|
|
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<class Op, typename... Args>
|
|
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<Itf,Op,std::decay_t<Args>...>(
|
|
std::forward<Args>(args)...);
|
|
this->insert({name, pfo});
|
|
}
|
|
|
|
/** Specialization for operators with empty constructors.
|
|
*/
|
|
template<class Op>
|
|
void add(const std::string& name)
|
|
{
|
|
eoForgeInterface<Itf>* pfo = new eoForgeOperator<Itf,Op>;
|
|
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<class Op, typename... Args>
|
|
void setup(const std::string& name, Args... args)
|
|
{
|
|
delete this->at(name); // Silent on nullptr.
|
|
auto pfo = new eoForgeOperator<Itf,Op,std::decay_t<Args>...>(
|
|
std::forward<Args>(args)...);
|
|
this->emplace({name, pfo});
|
|
}
|
|
|
|
/** Specialization for empty constructors.
|
|
*/
|
|
template<class Op>
|
|
void setup(const std::string& name)
|
|
{
|
|
delete this->at(name);
|
|
auto pfo = new eoForgeOperator<Itf,Op>;
|
|
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<double> factories(0.0, 1.0);
|
|
|
|
// Actually instantiate.
|
|
double param = factories.instantiate(0.5);
|
|
* @endcode
|
|
*
|
|
* @ingroup Foundry
|
|
*/
|
|
template<class Itf>
|
|
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_
|
|
|