53 #include "search_unit.hpp"
55 #include "algorithms/variable_heuristic.hpp"
56 #include "algorithms/variable_candidates_heuristic.hpp"
57 #include "algorithms/value_heuristic.hpp"
58 #include "algorithms/error_projection_heuristic.hpp"
60 #include "algorithms/adaptive_search_variable_heuristic.hpp"
61 #include "algorithms/adaptive_search_variable_candidates_heuristic.hpp"
62 #include "algorithms/adaptive_search_value_heuristic.hpp"
63 #include "algorithms/adaptive_search_error_projection_heuristic.hpp"
65 #include "algorithms/antidote_search_variable_heuristic.hpp"
66 #include "algorithms/antidote_search_variable_candidates_heuristic.hpp"
67 #include "algorithms/antidote_search_value_heuristic.hpp"
69 #include "algorithms/culprit_search_error_projection_heuristic.hpp"
109 template<
typename ModelBuilderType>
class Solver final
112 ModelBuilderType _model_builder;
114 int _number_variables;
115 int _number_constraints;
117 double _best_sat_error;
118 double _best_opt_cost;
119 double _cost_before_postprocess;
124 int _local_moves_total;
125 int _search_iterations_total;
126 int _local_minimum_total;
127 int _plateau_moves_total;
128 int _plateau_local_minimum_total;
134 int _search_iterations;
137 int _plateau_local_minimum;
139 std::string _variable_heuristic;
140 std::string _variable_candidates_heuristic;
141 std::string _value_heuristic;
142 std::string _error_projection_heuristic;
147 std::vector<std::vector<int> > _matrix_var_ctr;
154 std::vector< std::vector<int>> ac3_filtering(
int index_v, std::vector< std::vector<int>> domains )
157 std::deque<std::pair<int, int>> ac3queue;
159 for(
int constraint_id : _matrix_var_ctr[ index_v ] )
160 for(
int variable_id : _model.constraints[ constraint_id ]->_variables_index )
162 if( variable_id <= index_v )
165 ac3queue.push_back( std::make_pair( constraint_id, variable_id ) );
168 std::vector<int> values_to_remove;
169 while( !ac3queue.empty() )
171 int constraint_id = ac3queue.front().first;
172 int variable_id = ac3queue.front().second;
173 ac3queue.pop_front();
174 values_to_remove.clear();
175 for(
auto value : domains[variable_id] )
177 _model.variables[variable_id].set_value( value );
178 if( !has_support( constraint_id, variable_id, value, index_v, domains ) )
180 values_to_remove.push_back( value );
181 for(
int c_id : _matrix_var_ctr[ variable_id ] )
183 if( c_id == constraint_id )
186 for(
int v_id : _model.constraints[ c_id ]->_variables_index )
188 if( v_id <= index_v || v_id == variable_id )
191 if( std::find_if( ac3queue.begin(),
193 [&](
auto& elem ){ return elem.first == c_id && elem.second == v_id; } ) == ac3queue.end() )
195 ac3queue.push_back( std::make_pair( c_id, v_id ) );
202 for(
int value : values_to_remove )
203 domains[variable_id].erase( std::find( domains[variable_id].begin(), domains[variable_id].end(), value ) );
206 if( domains[variable_id].empty() )
217 bool has_support(
int constraint_id,
int variable_id,
int value,
int index_v,
const std::vector< std::vector<int>>& domains )
219 std::vector<int> constraint_scope;
220 for(
auto var_index : _model.constraints[ constraint_id ]->_variables_index )
221 if( var_index > index_v && var_index != variable_id )
222 constraint_scope.push_back( var_index );
225 if( constraint_scope.empty() )
226 return _model.constraints[ constraint_id ]->error() == 0.0;
229 std::vector<int> indexes( constraint_scope.size() + 1, 0 );
230 int fake_index =
static_cast<int>( indexes.size() ) - 1;
232 while( indexes[ fake_index ] == 0 )
234 for(
int i = 0 ; i < fake_index ; ++i )
236 int assignment_index = constraint_scope[i];
237 int assignment_value = domains[ assignment_index ][ indexes[ i ] ];
238 _model.variables[ assignment_index ].set_value( assignment_value );
241 if( _model.constraints[ constraint_id ]->error() == 0.0 )
251 if( index < fake_index && indexes[ index ] >=
static_cast<int>( domains[ constraint_scope[ index ] ].size() ) )
253 indexes[ index ] = 0;
268 std::vector<std::vector<int>> complete_search(
int index_v, std::vector< std::vector<int>> domains )
271 if( index_v >= _model.variables.size() )
272 return std::vector<std::vector<int>>();
274 std::vector< std::vector<int>> new_domains;
277 new_domains = ac3_filtering( index_v, domains );
278 auto empty_domain = std::find_if( new_domains.cbegin(), new_domains.cend(), [&](
auto& domain ){ return domain.empty(); } );
280 if( empty_domain != new_domains.cend() )
281 return std::vector<std::vector<int>>();
285 new_domains = domains;
288 int next_var = index_v + 1;
289 std::vector<std::vector<int>> solutions;
290 for(
auto value : new_domains[next_var] )
292 _model.variables[next_var].set_value( value );
295 if( next_var == _model.variables.size() - 1 )
297 std::vector<int> solution;
298 for(
auto& var : _model.variables )
299 solution.emplace_back( var.get_value() );
301 solutions.emplace_back( solution );
305 auto partial_solutions = complete_search( next_var, new_domains );
306 if( !partial_solutions.empty() )
307 std::copy_if( partial_solutions.begin(),
308 partial_solutions.end(),
309 std::back_inserter( solutions ),
310 [&](
auto& solution ){ return !solution.empty(); } );
325 Solver(
const ModelBuilderType& model_builder )
326 : _model_builder( model_builder ),
327 _best_sat_error( std::numeric_limits<double>::max() ),
328 _best_opt_cost( std::numeric_limits<double>::max() ),
329 _cost_before_postprocess( std::numeric_limits<double>::max() ),
330 _restarts_total( 0 ),
332 _local_moves_total( 0 ),
333 _search_iterations_total( 0 ),
334 _local_minimum_total( 0 ),
335 _plateau_moves_total( 0 ),
336 _plateau_local_minimum_total( 0 ),
340 _search_iterations( 0 ),
343 _plateau_local_minimum( 0 )
400 std::vector<int>& final_solution,
404 std::chrono::time_point<std::chrono::steady_clock> start_wall_clock( std::chrono::steady_clock::now() );
405 std::chrono::time_point<std::chrono::steady_clock> start_search;
406 std::chrono::time_point<std::chrono::steady_clock> start_postprocess;
407 std::chrono::duration<double,std::micro> elapsed_time( 0 );
408 std::chrono::duration<double,std::micro> timer_postprocess( 0 );
414 _model_builder.declare_variables();
415 _number_variables = _model_builder.get_number_variables();
420 _options.
tabu_time_local_min = std::max( std::min( 5,
static_cast<int>( _number_variables ) - 1 ),
static_cast<int>( std::ceil( _number_variables / 5 ) ) ) + 1;
443 double chrono_search;
444 double chrono_full_computation;
448 final_solution.resize( _number_variables );
449 bool solution_found =
false;
451 bool is_optimization;
453 #if defined GHOST_DEBUG || defined GHOST_TRACE || defined GHOST_BENCH
463 SearchUnit search_unit( _model_builder.build_model(),
466 is_optimization = search_unit.data.is_optimization;
467 std::future<bool> unit_future = search_unit.solution_found.get_future();
469 start_search = std::chrono::steady_clock::now();
470 search_unit.local_search( timeout );
471 elapsed_time = std::chrono::steady_clock::now() - start_search;
472 chrono_search = elapsed_time.count();
474 solution_found = unit_future.get();
475 _best_sat_error = search_unit.data.best_sat_error;
476 _best_opt_cost = search_unit.data.best_opt_cost;
477 _restarts = search_unit.data.restarts;
478 _resets = search_unit.data.resets;
479 _local_moves = search_unit.data.local_moves;
480 _search_iterations = search_unit.data.search_iterations;
481 _local_minimum = search_unit.data.local_minimum;
482 _plateau_moves = search_unit.data.plateau_moves;
483 _plateau_local_minimum = search_unit.data.plateau_local_minimum;
485 _variable_heuristic = search_unit.variable_heuristic->get_name();
486 _variable_candidates_heuristic = search_unit.variable_candidates_heuristic->get_name();
487 _value_heuristic = search_unit.value_heuristic->get_name();
488 _error_projection_heuristic = search_unit.error_projection_heuristic->get_name();
490 _model = std::move( search_unit.transfer_model() );
494 std::vector<SearchUnit> units;
496 std::vector<std::thread> unit_threads;
501 units.emplace_back( _model_builder.build_model(),
505 is_optimization = units[0].data.is_optimization;
507 std::vector<std::future<bool>> units_future;
508 std::vector<bool> units_terminated( _options.
number_threads,
false );
510 start_search = std::chrono::steady_clock::now();
514 unit_threads.emplace_back( &SearchUnit::local_search, &units.at(i), timeout );
515 units.at( i ).get_thread_id( unit_threads.at( i ).get_id() );
516 units_future.emplace_back( units.at( i ).solution_found.get_future() );
519 int thread_number = 0;
520 int winning_thread = 0;
521 bool end_of_computation =
false;
522 int number_timeouts = 0;
524 while( !end_of_computation )
526 for( thread_number = 0 ; thread_number < _options.
number_threads ; ++thread_number )
528 if( !units_terminated[ thread_number ] && units_future.at( thread_number ).wait_for( std::chrono::microseconds( 0 ) ) == std::future_status::ready )
530 if( is_optimization )
533 units_terminated[ thread_number ] =
true;
535 if( units_future.at( thread_number ).get() )
537 solution_found =
true;
538 if( _best_opt_cost > units.at( thread_number ).data.best_opt_cost )
540 _best_opt_cost = units.at( thread_number ).data.best_opt_cost;
541 winning_thread = thread_number;
547 end_of_computation =
true;
553 if( units_future.at( thread_number ).get() )
555 solution_found =
true;
556 units_terminated[ thread_number ] =
true;
557 winning_thread = thread_number;
558 end_of_computation =
true;
564 units_terminated[ thread_number ] =
true;
567 end_of_computation =
true;
576 elapsed_time = std::chrono::steady_clock::now() - start_search;
577 chrono_search = elapsed_time.count();
583 units.at(i).stop_search();
585 _restarts_total += units.at(i).data.restarts;
586 _resets_total += units.at(i).data.resets;
587 _local_moves_total += units.at(i).data.local_moves;
588 _search_iterations_total += units.at(i).data.search_iterations;
589 _local_minimum_total += units.at(i).data.local_minimum;
590 _plateau_moves_total += units.at(i).data.plateau_moves;
591 _plateau_local_minimum_total += units.at(i).data.plateau_local_minimum;
597 #if defined GHOST_TRACE
598 std::cout <<
"Parallel run, thread number " << winning_thread <<
" has found a solution.\n";
600 _best_sat_error = units.at( winning_thread ).data.best_sat_error;
601 _best_opt_cost = units.at( winning_thread ).data.best_opt_cost;
603 _restarts = units.at( winning_thread ).data.restarts;
604 _resets = units.at( winning_thread ).data.resets;
605 _local_moves = units.at( winning_thread ).data.local_moves;
606 _search_iterations = units.at( winning_thread ).data.search_iterations;
607 _local_minimum = units.at( winning_thread ).data.local_minimum;
608 _plateau_moves = units.at( winning_thread ).data.plateau_moves;
609 _plateau_local_minimum = units.at( winning_thread ).data.plateau_local_minimum;
611 _variable_heuristic = units.at( winning_thread ).variable_heuristic->get_name();
612 _variable_candidates_heuristic = units.at( winning_thread ).variable_candidates_heuristic->get_name();
613 _value_heuristic = units.at( winning_thread ).value_heuristic->get_name();
614 _error_projection_heuristic = units.at( winning_thread ).error_projection_heuristic->get_name();
616 _model = std::move( units.at( winning_thread ).transfer_model() );
620 #if defined GHOST_TRACE
621 std::cout <<
"Parallel run, no solutions found.\n";
623 int best_non_solution = 0;
626 if( _best_sat_error > units.at( i ).data.best_sat_error )
628 best_non_solution = i;
629 _best_sat_error = units.at( i ).data.best_sat_error;
631 if( is_optimization && _best_sat_error == 0.0 )
632 if( units.at( i ).data.best_sat_error == 0.0 && _best_opt_cost > units.at( i ).data.best_opt_cost )
634 best_non_solution = i;
635 _best_opt_cost = units.at( i ).data.best_opt_cost;
639 _restarts = units.at( best_non_solution ).data.restarts;
640 _resets = units.at( best_non_solution ).data.resets;
641 _local_moves = units.at( best_non_solution ).data.local_moves;
642 _search_iterations = units.at( best_non_solution ).data.search_iterations;
643 _local_minimum = units.at( best_non_solution ).data.local_minimum;
644 _plateau_moves = units.at( best_non_solution ).data.plateau_moves;
645 _plateau_local_minimum = units.at( best_non_solution ).data.plateau_local_minimum;
647 _variable_heuristic = units.at( best_non_solution ).variable_heuristic->get_name();
648 _variable_candidates_heuristic = units.at( best_non_solution ).variable_candidates_heuristic->get_name();
649 _value_heuristic = units.at( best_non_solution ).value_heuristic->get_name();
650 _error_projection_heuristic = units.at( best_non_solution ).error_projection_heuristic->get_name();
652 _model = std::move( units.at( best_non_solution ).transfer_model() );
655 for(
auto& thread: unit_threads )
657 #if defined GHOST_TRACE
658 std::cout <<
"Joining and terminating thread number " << thread.get_id() <<
"\n";
664 if( solution_found && is_optimization )
666 _cost_before_postprocess = _best_opt_cost;
668 start_postprocess = std::chrono::steady_clock::now();
669 _best_opt_cost = _model.objective->postprocess( _best_opt_cost );
670 timer_postprocess = std::chrono::steady_clock::now() - start_postprocess;
673 if( is_optimization )
675 if( _model.objective->is_maximization() )
677 _best_opt_cost = -_best_opt_cost;
678 _cost_before_postprocess = -_cost_before_postprocess;
681 final_cost = _best_opt_cost;
684 final_cost = _best_sat_error;
686 std::transform( _model.variables.begin(),
687 _model.variables.end(),
688 final_solution.begin(),
689 [&](
auto& var){ return var.get_value(); } );
691 elapsed_time = std::chrono::steady_clock::now() - start_wall_clock;
692 chrono_full_computation = elapsed_time.count();
694 #if defined GHOST_DEBUG || defined GHOST_TRACE || defined GHOST_BENCH
695 std::cout <<
"@@@@@@@@@@@@" <<
"\n"
696 <<
"Variable heuristic: " << _variable_heuristic <<
"\n"
697 <<
"Variable candidate heuristic: " << _variable_candidates_heuristic <<
"\n"
698 <<
"Value heuristic: " << _value_heuristic <<
"\n"
699 <<
"Error projection heuristic: " << _error_projection_heuristic <<
"\n"
700 <<
"Started from a custom variables assignment: " << std::boolalpha << _options.
custom_starting_point <<
"\n"
701 <<
"Search resumed from a previous run: " << std::boolalpha << _options.
resume_search <<
"\n"
702 <<
"Parallel search: " << std::boolalpha << _options.
parallel_runs <<
"\n"
703 <<
"Number of threads (not used if no parallel search): " << _options.
number_threads <<
"\n"
704 <<
"Number of variable assignments samplings at start (if custom start and resume are set to false): " << _options.
number_start_samplings <<
"\n"
705 <<
"Variables of local minimum are frozen for: " << _options.
tabu_time_local_min <<
" local moves\n"
706 <<
"Selected variables are frozen for: " << _options.
tabu_time_selected <<
" local moves\n"
710 std::cout <<
"Do a restart each time " << _options.
restart_threshold <<
" resets are performed\n";
712 std::cout <<
"Never perform restarts\n";
713 std::cout <<
"############" <<
"\n";
716 std::cout << _options.
print->print_candidate( _model.variables ).str();
720 if( !is_optimization )
721 std::cout <<
"SATISFACTION run" <<
"\n";
724 std::cout <<
"OPTIMIZATION run with objective " << _model.objective->get_name() <<
"\n";
725 if( _model.objective->is_maximization() )
726 std::cout << _model.objective->get_name() <<
" must be maximized.\n";
728 std::cout << _model.objective->get_name() <<
" must be minimized.\n";
731 std::cout <<
"Permutation problem: " << std::boolalpha << _model.permutation_problem <<
"\n"
732 <<
"Time budget: " << timeout <<
"us (= " << timeout/1000 <<
"ms, " << timeout/1000000 <<
"s)\n"
733 <<
"Search time: " << chrono_search <<
"us (= " << chrono_search / 1000 <<
"ms, " << chrono_search / 1000000 <<
"s)\n"
734 <<
"Wall-clock time (full call): " << chrono_full_computation <<
"us (= " << chrono_full_computation/1000 <<
"ms, " << chrono_full_computation/1000000 <<
"s)\n"
735 <<
"Satisfaction error: " << _best_sat_error <<
"\n"
736 <<
"Number of search iterations: " << _search_iterations <<
"\n"
737 <<
"Number of local moves: " << _local_moves <<
" (including on plateau: " << _plateau_moves <<
")\n"
738 <<
"Number of local minimum: " << _local_minimum <<
" (including on plateau: " << _plateau_local_minimum <<
")\n"
739 <<
"Number of resets: " << _resets <<
"\n"
740 <<
"Number of restarts: " << _restarts <<
"\n";
743 std::cout <<
"Total number of search iterations: " << _search_iterations_total <<
"\n"
744 <<
"Total number of local moves: " << _local_moves_total <<
" (including on plateau: " << _plateau_moves_total <<
")\n"
745 <<
"Total number of local minimum: " << _local_minimum_total <<
" (including on plateau: " << _plateau_local_minimum_total <<
")\n"
746 <<
"Total number of resets: " << _resets_total <<
"\n"
747 <<
"Total number of restarts: " << _restarts_total <<
"\n";
749 if( is_optimization )
750 std::cout <<
"\nOptimization cost: " << _best_opt_cost <<
"\n";
754 if( timer_postprocess.count() > 1 )
755 std::cout <<
"Optimization Cost BEFORE post-processing: " << _cost_before_postprocess <<
"\n"
756 <<
"Optimization post-processing time: " << timer_postprocess.count() <<
"us (= " << timer_postprocess.count()/1000 <<
"ms, " << timer_postprocess.count()/1000000 <<
"s)\n";
761 return solution_found;
777 bool fast_search(
double& final_cost, std::vector<int>& final_solution,
double timeout )
780 return fast_search( final_cost, final_solution, timeout, options );
803 bool fast_search(
double& final_cost, std::vector<int>& final_solution, std::chrono::microseconds timeout,
Options& options )
805 return fast_search( final_cost, final_solution, timeout.count(), options );
824 bool fast_search(
double& final_cost, std::vector<int>& final_solution, std::chrono::microseconds timeout )
827 return fast_search( final_cost, final_solution, timeout, options );
859 std::vector<std::vector<int>>& final_solutions,
863 bool solutions_exist =
false;
866 _model = _model_builder.build_model();
868 std::vector< std::vector<int> > domains;
869 for(
auto& var : _model.variables )
870 domains.emplace_back( var.get_full_domain() );
872 _matrix_var_ctr.reserve( _model.variables.size() );
873 for(
int variable_id = 0; variable_id < static_cast<int>( _model.variables.size() ); ++variable_id )
874 for(
int constraint_id = 0; constraint_id < static_cast<int>( _model.constraints.size() ); ++constraint_id )
875 if( _model.constraints[ constraint_id ]->has_variable( variable_id ) )
876 _matrix_var_ctr[ variable_id ].push_back( constraint_id );
878 for(
int value : domains[0] )
880 _model.variables[0].set_value( value );
881 auto new_domains = ac3_filtering( 0, domains );
882 auto empty_domain = std::find_if( new_domains.cbegin(), new_domains.cend(), [&](
auto& domain ){ return domain.empty(); } );
884 if( empty_domain == new_domains.cend() )
886 std::vector<std::vector<int>> partial_solutions = complete_search( 0, new_domains );
888 for(
auto& solution : partial_solutions )
889 if( !solution.empty() )
891 solutions_exist =
true;
892 for(
int i = 1 ; i < static_cast<int>( solution.size() ) ; ++i )
893 _model.variables[i].set_value( solution[i] );
894 final_costs.push_back( _model.objective->cost() );
895 final_solutions.emplace_back( solution );
902 return solutions_exist;
917 bool complete_search( std::vector<double>& final_costs, std::vector<std::vector<int>>& final_solutions )
920 return complete_search( final_costs, final_solutions, options );
Definition: solver.hpp:110
Solver(const ModelBuilderType &model_builder)
Definition: solver.hpp:325
bool fast_search(double &final_cost, std::vector< int > &final_solution, double timeout, Options &options)
Definition: solver.hpp:399
bool fast_search(double &final_cost, std::vector< int > &final_solution, std::chrono::microseconds timeout)
Definition: solver.hpp:824
bool complete_search(std::vector< double > &final_costs, std::vector< std::vector< int >> &final_solutions, Options &options)
Definition: solver.hpp:858
bool fast_search(double &final_cost, std::vector< int > &final_solution, double timeout)
Definition: solver.hpp:777
bool fast_search(double &final_cost, std::vector< int > &final_solution, std::chrono::microseconds timeout, Options &options)
Definition: solver.hpp:803
bool complete_search(std::vector< double > &final_costs, std::vector< std::vector< int >> &final_solutions)
Definition: solver.hpp:917
std::vector< Variable > get_variables()
Definition: solver.hpp:929
Definition: auxiliary_data.hpp:38
Definition: options.hpp:45
int tabu_time_selected
Number of local moves a selected variable is marked tabu.
Definition: options.hpp:52
int reset_threshold
Number of variables marked as tabu required to trigger a reset.
Definition: options.hpp:54
bool parallel_runs
To enable parallel runs of the solver. Using all available physical cores if number_threads is not sp...
Definition: options.hpp:48
int number_threads
Number of threads the solver will use for the search.
Definition: options.hpp:49
int number_start_samplings
Number of variable assignments the solver randomly draw, if custom_starting_point and resume_search a...
Definition: options.hpp:57
int number_variables_to_reset
Number of variables to randomly change the value at each reset.
Definition: options.hpp:56
bool custom_starting_point
To force starting the search on a custom variables assignment.
Definition: options.hpp:46
std::shared_ptr< Print > print
Allowing custom solution print (by derivating a class from ghost::Print)
Definition: options.hpp:50
int restart_threshold
Trigger a restart every 'restart_threshold' reset. Set to 0 to never trigger restarts.
Definition: options.hpp:55
int percent_chance_escape_plateau
Percentage of chance to espace a (1-dimension, ie, related to 1 variable) plateau rather than explori...
Definition: options.hpp:53
int tabu_time_local_min
Number of local moves a variable of a local minimum is marked tabu.
Definition: options.hpp:51
bool resume_search
Allowing stop-and-resume computation.
Definition: options.hpp:47