|
|
|
|
|
#ifndef DLIB_FiND_GLOBAL_MAXIMUM_hH_ |
|
#define DLIB_FiND_GLOBAL_MAXIMUM_hH_ |
|
|
|
#include "find_max_global_abstract.h" |
|
#include "global_function_search.h" |
|
#include "../metaprogramming.h" |
|
#include <utility> |
|
#include <chrono> |
|
#include <memory> |
|
#include <thread> |
|
#include <functional> |
|
#include "../threads/thread_pool_extension.h" |
|
#include "../statistics/statistics.h" |
|
#include "../enable_if.h" |
|
|
|
namespace dlib |
|
{ |
|
namespace gopt_impl |
|
{ |
|
|
|
|
|
|
|
class disable_decay_to_scalar |
|
{ |
|
const matrix<double,0,1>& a; |
|
public: |
|
disable_decay_to_scalar(const matrix<double,0,1>& a) : a(a){} |
|
operator const matrix<double,0,1>&() const { return a;} |
|
}; |
|
|
|
|
|
template <typename T, size_t... indices> |
|
auto _cwv ( |
|
T&& f, |
|
const matrix<double,0,1>& a, |
|
compile_time_integer_list<indices...> |
|
) -> decltype(f(a(indices-1)...)) |
|
{ |
|
DLIB_CASSERT(a.size() == sizeof...(indices), |
|
"You invoked dlib::call_function_and_expand_args(f,a) but the number of arguments expected by f() doesn't match the size of 'a'. " |
|
<< "Expected " << sizeof...(indices) << " arguments but got " << a.size() << "." |
|
); |
|
return f(a(indices-1)...); |
|
} |
|
|
|
|
|
|
|
#ifndef _MSC_VER |
|
template <size_t max_unpack> |
|
struct call_function_and_expand_args |
|
{ |
|
template <typename T> |
|
static auto go(T&& f, const matrix<double,0,1>& a) -> decltype(_cwv(std::forward<T>(f),a,typename make_compile_time_integer_range<max_unpack>::type())) |
|
{ |
|
return _cwv(std::forward<T>(f),a,typename make_compile_time_integer_range<max_unpack>::type()); |
|
} |
|
|
|
template <typename T> |
|
static auto go(T&& f, const matrix<double,0,1>& a) -> decltype(call_function_and_expand_args<max_unpack-1>::template go(std::forward<T>(f),a)) |
|
{ |
|
return call_function_and_expand_args<max_unpack-1>::go(std::forward<T>(f),a); |
|
} |
|
}; |
|
|
|
template <> |
|
struct call_function_and_expand_args<0> |
|
{ |
|
template <typename T> |
|
static auto go(T&& f, const matrix<double,0,1>& a) -> decltype(f(disable_decay_to_scalar(a))) |
|
{ |
|
return f(disable_decay_to_scalar(a)); |
|
} |
|
}; |
|
#else |
|
template <size_t max_unpack> |
|
struct call_function_and_expand_args |
|
{ |
|
template <typename T> static auto go(T&& f, const matrix<double, 0, 1>& a) -> decltype(f(disable_decay_to_scalar(a))) {return f(disable_decay_to_scalar(a)); } |
|
template <typename T> static auto go(T&& f, const matrix<double, 0, 1>& a) -> decltype(f(a(0))) { DLIB_CASSERT(a.size() == 1); return f(a(0)); } |
|
template <typename T> static auto go(T&& f, const matrix<double, 0, 1>& a) -> decltype(f(a(0),a(1))) { DLIB_CASSERT(a.size() == 2); return f(a(0),a(1)); } |
|
template <typename T> static auto go(T&& f, const matrix<double, 0, 1>& a) -> decltype(f(a(0), a(1), a(2))) { DLIB_CASSERT(a.size() == 3); return f(a(0), a(1),a(2)); } |
|
template <typename T> static auto go(T&& f, const matrix<double, 0, 1>& a) -> decltype(f(a(0), a(1), a(2), a(3))) { DLIB_CASSERT(a.size() == 4); return f(a(0), a(1), a(2), a(3)); } |
|
template <typename T> static auto go(T&& f, const matrix<double, 0, 1>& a) -> decltype(f(a(0), a(1), a(2), a(3), a(4))) { DLIB_CASSERT(a.size() == 5); return f(a(0), a(1), a(2), a(3), a(4)); } |
|
template <typename T> static auto go(T&& f, const matrix<double, 0, 1>& a) -> decltype(f(a(0), a(1), a(2), a(3), a(4), a(5))) { DLIB_CASSERT(a.size() == 6); return f(a(0), a(1), a(2), a(3), a(4), a(5)); } |
|
template <typename T> static auto go(T&& f, const matrix<double, 0, 1>& a) -> decltype(f(a(0), a(1), a(2), a(3), a(4), a(5), a(6))) { DLIB_CASSERT(a.size() == 7); return f(a(0), a(1), a(2), a(3), a(4), a(5), a(6)); } |
|
}; |
|
#endif |
|
} |
|
|
|
|
|
|
|
|
|
template <typename T> |
|
auto call_function_and_expand_args( |
|
T&& f, |
|
const matrix<double,0,1>& a |
|
) -> decltype(gopt_impl::call_function_and_expand_args<40>::go(f,a)) |
|
{ |
|
|
|
return gopt_impl::call_function_and_expand_args<40>::go(std::forward<T>(f),a); |
|
} |
|
|
|
|
|
|
|
|
|
struct max_function_calls |
|
{ |
|
max_function_calls() = default; |
|
explicit max_function_calls(size_t max_calls) : max_calls(max_calls) {} |
|
size_t max_calls = std::numeric_limits<size_t>::max(); |
|
}; |
|
|
|
|
|
|
|
const auto FOREVER = std::chrono::hours(24*365*290); |
|
using stop_condition = std::function<bool(double)>; |
|
const stop_condition never_stop_early = [](double) { return false; }; |
|
|
|
|
|
|
|
namespace impl |
|
{ |
|
template < |
|
typename funct |
|
> |
|
std::pair<size_t,function_evaluation> find_max_global ( |
|
double ymult, |
|
thread_pool& tp, |
|
std::vector<funct>& functions, |
|
std::vector<function_spec> specs, |
|
const max_function_calls num, |
|
const std::chrono::nanoseconds max_runtime = FOREVER, |
|
double solver_epsilon = 0, |
|
std::vector<std::vector<function_evaluation>> initial_function_evals = {}, |
|
stop_condition should_stop = never_stop_early |
|
) |
|
{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<std::vector<bool>> log_scale(specs.size()); |
|
for (size_t i = 0; i < specs.size(); ++i) |
|
{ |
|
for (long j = 0; j < specs[i].lower.size(); ++j) |
|
{ |
|
if (!specs[i].is_integer_variable[j] && specs[i].lower(j) > 0 && specs[i].upper(j)/specs[i].lower(j) >= 1000) |
|
{ |
|
log_scale[i].push_back(true); |
|
specs[i].lower(j) = std::log(specs[i].lower(j)); |
|
specs[i].upper(j) = std::log(specs[i].upper(j)); |
|
} |
|
else |
|
{ |
|
log_scale[i].push_back(false); |
|
} |
|
} |
|
} |
|
|
|
if (initial_function_evals.empty()) |
|
{ |
|
initial_function_evals.resize(specs.size()); |
|
} |
|
for (auto& evals : initial_function_evals) { |
|
for (auto& eval : evals) { |
|
eval.y *= ymult; |
|
} |
|
} |
|
|
|
global_function_search opt(specs, {initial_function_evals}); |
|
opt.set_solver_epsilon(solver_epsilon); |
|
|
|
running_stats_decayed<double> objective_funct_eval_time(functions.size()*5); |
|
std::mutex eval_time_mutex; |
|
using namespace std::chrono; |
|
|
|
const auto time_to_stop = steady_clock::now() + max_runtime; |
|
|
|
std::atomic<int> this_should_stop{false}; |
|
|
|
double max_solver_overhead_time = 0; |
|
|
|
|
|
for (size_t i = 0; i < num.max_calls && steady_clock::now() < time_to_stop && !this_should_stop.load(); ++i) |
|
{ |
|
const auto get_next_x_start_time = steady_clock::now(); |
|
auto next = std::make_shared<function_evaluation_request>(opt.get_next_x()); |
|
const auto get_next_x_runtime = steady_clock::now() - get_next_x_start_time; |
|
|
|
auto execute_call = [&functions,&ymult,&log_scale,&eval_time_mutex,&objective_funct_eval_time,next,&should_stop,&this_should_stop]() { |
|
matrix<double,0,1> x = next->x(); |
|
|
|
|
|
for (long j = 0; j < x.size(); ++j) |
|
{ |
|
if (log_scale[next->function_idx()][j]) |
|
x(j) = std::exp(x(j)); |
|
} |
|
const auto funct_eval_start = steady_clock::now(); |
|
double y = ymult*call_function_and_expand_args(functions[next->function_idx()], x); |
|
const double funct_eval_runtime = duration_cast<nanoseconds>(steady_clock::now() - funct_eval_start).count(); |
|
this_should_stop.fetch_or(should_stop(y*ymult)); |
|
next->set(y); |
|
|
|
std::lock_guard<std::mutex> lock(eval_time_mutex); |
|
objective_funct_eval_time.add(funct_eval_runtime); |
|
}; |
|
|
|
tp.add_task_by_value(execute_call); |
|
|
|
std::lock_guard<std::mutex> lock(eval_time_mutex); |
|
const double obj_funct_time = objective_funct_eval_time.mean()/std::max(1ul,tp.num_threads_in_pool()); |
|
const double solver_overhead_time = duration_cast<nanoseconds>(get_next_x_runtime).count(); |
|
max_solver_overhead_time = std::max(max_solver_overhead_time, solver_overhead_time); |
|
|
|
|
|
|
|
|
|
if (objective_funct_eval_time.current_n() > functions.size()*5) |
|
{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (obj_funct_time < solver_overhead_time) |
|
{ |
|
|
|
|
|
const size_t new_val = static_cast<size_t>(std::floor(opt.get_monte_carlo_upper_bound_sample_num()*0.8)); |
|
opt.set_monte_carlo_upper_bound_sample_num(std::max<size_t>(1, new_val)); |
|
|
|
|
|
|
|
if (opt.get_monte_carlo_upper_bound_sample_num() == 1) |
|
{ |
|
opt.set_pure_random_search_probability(1); |
|
} |
|
} else if (obj_funct_time > 1.5*max_solver_overhead_time) |
|
{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
const size_t new_val = static_cast<size_t>(std::ceil(opt.get_monte_carlo_upper_bound_sample_num()*1.28)); |
|
opt.set_monte_carlo_upper_bound_sample_num(std::min<size_t>(5000, new_val)); |
|
|
|
opt.set_pure_random_search_probability(0.02); |
|
} |
|
} |
|
} |
|
tp.wait_for_all_tasks(); |
|
|
|
|
|
matrix<double,0,1> x; |
|
double y; |
|
size_t function_idx; |
|
opt.get_best_function_eval(x,y,function_idx); |
|
|
|
for (long j = 0; j < x.size(); ++j) |
|
{ |
|
if (log_scale[function_idx][j]) |
|
x(j) = std::exp(x(j)); |
|
} |
|
return std::make_pair(function_idx, function_evaluation(x,y/ymult)); |
|
} |
|
|
|
|
|
template < |
|
typename funct, |
|
typename ...Args |
|
> |
|
std::pair<size_t,function_evaluation> find_max_global ( |
|
double ymult, |
|
thread_pool& tp, |
|
std::vector<funct>& functions, |
|
std::vector<function_spec> specs, |
|
const std::chrono::nanoseconds max_runtime, |
|
const max_function_calls num, |
|
double solver_epsilon = 0, |
|
Args&& ...args |
|
) |
|
{ |
|
return find_max_global(ymult, tp, functions, std::move(specs), num, max_runtime, solver_epsilon, std::forward<Args>(args)...); |
|
} |
|
|
|
|
|
template < |
|
typename funct, |
|
typename ...Args |
|
> |
|
std::pair<size_t,function_evaluation> find_max_global ( |
|
double ymult, |
|
thread_pool& tp, |
|
std::vector<funct>& functions, |
|
std::vector<function_spec> specs, |
|
const std::chrono::nanoseconds max_runtime, |
|
double solver_epsilon = 0, |
|
Args&& ...args |
|
) |
|
{ |
|
return find_max_global(ymult, tp, functions, std::move(specs), max_function_calls(), max_runtime, solver_epsilon, std::forward<Args>(args)...); |
|
} |
|
|
|
|
|
template < |
|
typename funct, |
|
typename ...Args |
|
> |
|
std::pair<size_t,function_evaluation> find_max_global ( |
|
double ymult, |
|
thread_pool& tp, |
|
std::vector<funct>& functions, |
|
std::vector<function_spec> specs, |
|
const max_function_calls num, |
|
double solver_epsilon, |
|
Args&& ...args |
|
) |
|
{ |
|
return find_max_global(ymult, tp, functions, std::move(specs), num, FOREVER, solver_epsilon, std::forward<Args>(args)...); |
|
} |
|
|
|
|
|
template < |
|
typename funct, |
|
typename ...Args |
|
> |
|
std::pair<size_t,function_evaluation> find_max_global ( |
|
double ymult, |
|
std::vector<funct>& functions, |
|
Args&& ...args |
|
) |
|
{ |
|
|
|
thread_pool tp(0); |
|
|
|
return find_max_global(ymult, tp, functions, std::forward<Args>(args)...); |
|
} |
|
|
|
|
|
|
|
|
|
template <typename T> |
|
const T& normalize(const T& item) |
|
{ |
|
return item; |
|
} |
|
|
|
inline std::vector<std::vector<function_evaluation>> normalize( |
|
const std::vector<function_evaluation>& initial_function_evals |
|
) |
|
{ |
|
return {initial_function_evals}; |
|
} |
|
} |
|
|
|
|
|
|
|
template < |
|
typename funct, |
|
typename ...Args |
|
> |
|
std::pair<size_t,function_evaluation> find_max_global ( |
|
std::vector<funct>& functions, |
|
std::vector<function_spec> specs, |
|
Args&& ...args |
|
) |
|
{ |
|
return impl::find_max_global(+1, functions, std::move(specs), std::forward<Args>(args)...); |
|
} |
|
|
|
template < |
|
typename funct, |
|
typename ...Args |
|
> |
|
std::pair<size_t,function_evaluation> find_min_global ( |
|
std::vector<funct>& functions, |
|
std::vector<function_spec> specs, |
|
Args&& ...args |
|
) |
|
{ |
|
return impl::find_max_global(-1, functions, std::move(specs), std::forward<Args>(args)...); |
|
} |
|
|
|
template < |
|
typename funct, |
|
typename ...Args |
|
> |
|
std::pair<size_t,function_evaluation> find_max_global ( |
|
thread_pool& tp, |
|
std::vector<funct>& functions, |
|
std::vector<function_spec> specs, |
|
Args&& ...args |
|
) |
|
{ |
|
return impl::find_max_global(+1, tp, functions, std::move(specs), std::forward<Args>(args)...); |
|
} |
|
|
|
template < |
|
typename funct, |
|
typename ...Args |
|
> |
|
std::pair<size_t,function_evaluation> find_min_global ( |
|
thread_pool& tp, |
|
std::vector<funct>& functions, |
|
std::vector<function_spec> specs, |
|
Args&& ...args |
|
) |
|
{ |
|
return impl::find_max_global(-1, tp, functions, std::move(specs), std::forward<Args>(args)...); |
|
} |
|
|
|
|
|
|
|
|
|
template < |
|
typename funct, |
|
typename ...Args |
|
> |
|
function_evaluation find_max_global ( |
|
funct f, |
|
const matrix<double,0,1>& bound1, |
|
const matrix<double,0,1>& bound2, |
|
const std::vector<bool>& is_integer_variable, |
|
Args&& ...args |
|
) |
|
{ |
|
std::vector<funct> functions(1,std::move(f)); |
|
std::vector<function_spec> specs(1, function_spec(bound1, bound2, is_integer_variable)); |
|
return find_max_global(functions, std::move(specs), impl::normalize(args)...).second; |
|
} |
|
|
|
template < |
|
typename funct, |
|
typename ...Args |
|
> |
|
function_evaluation find_min_global ( |
|
funct f, |
|
const matrix<double,0,1>& bound1, |
|
const matrix<double,0,1>& bound2, |
|
const std::vector<bool>& is_integer_variable, |
|
Args&& ...args |
|
) |
|
{ |
|
std::vector<funct> functions(1,std::move(f)); |
|
std::vector<function_spec> specs(1, function_spec(bound1, bound2, is_integer_variable)); |
|
return find_min_global(functions, std::move(specs), impl::normalize(args)...).second; |
|
} |
|
|
|
template < |
|
typename funct, |
|
typename ...Args |
|
> |
|
function_evaluation find_max_global ( |
|
thread_pool& tp, |
|
funct f, |
|
const matrix<double,0,1>& bound1, |
|
const matrix<double,0,1>& bound2, |
|
const std::vector<bool>& is_integer_variable, |
|
Args&& ...args |
|
) |
|
{ |
|
std::vector<funct> functions(1,std::move(f)); |
|
std::vector<function_spec> specs(1, function_spec(bound1, bound2, is_integer_variable)); |
|
return find_max_global(tp, functions, std::move(specs), impl::normalize(args)...).second; |
|
} |
|
|
|
template < |
|
typename funct, |
|
typename ...Args |
|
> |
|
function_evaluation find_min_global ( |
|
thread_pool& tp, |
|
funct f, |
|
const matrix<double,0,1>& bound1, |
|
const matrix<double,0,1>& bound2, |
|
const std::vector<bool>& is_integer_variable, |
|
Args&& ...args |
|
) |
|
{ |
|
std::vector<funct> functions(1,std::move(f)); |
|
std::vector<function_spec> specs(1, function_spec(bound1, bound2, is_integer_variable)); |
|
return find_min_global(tp, functions, std::move(specs), impl::normalize(args)...).second; |
|
} |
|
|
|
|
|
|
|
|
|
template < |
|
typename funct, |
|
typename T, |
|
typename ...Args |
|
> |
|
typename disable_if<std::is_same<T,std::vector<bool>>, function_evaluation>::type |
|
find_max_global ( |
|
funct f, |
|
const matrix<double,0,1>& bound1, |
|
const matrix<double,0,1>& bound2, |
|
const T& arg, |
|
Args&& ...args |
|
) |
|
{ |
|
const std::vector<bool> is_integer_variable(bound1.size(),false); |
|
return find_max_global(std::move(f), bound1, bound2, is_integer_variable, arg, impl::normalize(args)...); |
|
} |
|
|
|
template < |
|
typename funct, |
|
typename T, |
|
typename ...Args |
|
> |
|
typename disable_if<std::is_same<T,std::vector<bool>>, function_evaluation>::type |
|
find_min_global ( |
|
funct f, |
|
const matrix<double,0,1>& bound1, |
|
const matrix<double,0,1>& bound2, |
|
const T& arg, |
|
Args&& ...args |
|
) |
|
{ |
|
const std::vector<bool> is_integer_variable(bound1.size(),false); |
|
return find_min_global(std::move(f), bound1, bound2, is_integer_variable, arg, impl::normalize(args)...); |
|
} |
|
|
|
template < |
|
typename funct, |
|
typename T, |
|
typename ...Args |
|
> |
|
typename disable_if<std::is_same<T,std::vector<bool>>, function_evaluation>::type |
|
find_max_global ( |
|
thread_pool& tp, |
|
funct f, |
|
const matrix<double,0,1>& bound1, |
|
const matrix<double,0,1>& bound2, |
|
const T& arg, |
|
Args&& ...args |
|
) |
|
{ |
|
const std::vector<bool> is_integer_variable(bound1.size(),false); |
|
return find_max_global(tp, std::move(f), bound1, bound2, is_integer_variable, arg, impl::normalize(args)...); |
|
} |
|
|
|
template < |
|
typename funct, |
|
typename T, |
|
typename ...Args |
|
> |
|
typename disable_if<std::is_same<T,std::vector<bool>>, function_evaluation>::type |
|
find_min_global ( |
|
thread_pool& tp, |
|
funct f, |
|
const matrix<double,0,1>& bound1, |
|
const matrix<double,0,1>& bound2, |
|
const T& arg, |
|
Args&& ...args |
|
) |
|
{ |
|
const std::vector<bool> is_integer_variable(bound1.size(),false); |
|
return find_min_global(tp, std::move(f), bound1, bound2, is_integer_variable, arg, impl::normalize(args)...); |
|
} |
|
|
|
|
|
|
|
|
|
template < |
|
typename funct, |
|
typename T, |
|
typename ...Args |
|
> |
|
function_evaluation find_max_global ( |
|
funct f, |
|
const double bound1, |
|
const double bound2, |
|
const T& arg, |
|
Args&& ...args |
|
) |
|
{ |
|
return find_max_global(std::move(f), matrix<double,0,1>({bound1}), matrix<double,0,1>({bound2}), arg, impl::normalize(args)...); |
|
} |
|
|
|
template < |
|
typename funct, |
|
typename T, |
|
typename ...Args |
|
> |
|
function_evaluation find_min_global ( |
|
funct f, |
|
const double bound1, |
|
const double bound2, |
|
const T& arg, |
|
Args&& ...args |
|
) |
|
{ |
|
return find_min_global(std::move(f), matrix<double,0,1>({bound1}), matrix<double,0,1>({bound2}), arg, impl::normalize(args)...); |
|
} |
|
|
|
template < |
|
typename funct, |
|
typename T, |
|
typename ...Args |
|
> |
|
function_evaluation find_max_global ( |
|
thread_pool& tp, |
|
funct f, |
|
const double bound1, |
|
const double bound2, |
|
const T& arg, |
|
Args&& ...args |
|
) |
|
{ |
|
return find_max_global(tp, std::move(f), matrix<double,0,1>({bound1}), matrix<double,0,1>({bound2}), arg, impl::normalize(args)...); |
|
} |
|
|
|
template < |
|
typename funct, |
|
typename T, |
|
typename ...Args |
|
> |
|
function_evaluation find_min_global ( |
|
thread_pool& tp, |
|
funct f, |
|
const double bound1, |
|
const double bound2, |
|
const T& arg, |
|
Args&& ...args |
|
) |
|
{ |
|
return find_min_global(tp, std::move(f), matrix<double,0,1>({bound1}), matrix<double,0,1>({bound2}), arg, impl::normalize(args)...); |
|
} |
|
|
|
|
|
|
|
} |
|
|
|
#endif |
|
|
|
|