GHOST
solver.hpp
Go to the documentation of this file.
1 /*
2  * GHOST (General meta-Heuristic Optimization Solving Tool) is a C++ framework
3  * designed to help developers to model and implement optimization problem
4  * solving. It contains a meta-heuristic solver aiming to solve any kind of
5  * combinatorial and optimization real-time problems represented by a CSP/COP/EF-CSP/EF-COP.
6  *
7  * First developed to solve game-related optimization problems, GHOST can be used for
8  * any kind of applications where solving combinatorial and optimization problems. In
9  * particular, it had been designed to be able to solve not-too-complex problem instances
10  * within some milliseconds, making it very suitable for highly reactive or embedded systems.
11  * Please visit https://github.com/richoux/GHOST for further information.
12  *
13  * Copyright (C) 2014-2023 Florian Richoux
14  *
15  * This file is part of GHOST.
16  * GHOST is free software: you can redistribute it and/or
17  * modify it under the terms of the GNU General Public License as published
18  * by the Free Software Foundation, either version 3 of the License, or
19  * (at your option) any later version.
20 
21  * GHOST is distributed in the hope that it will be useful,
22  * but WITHOUT ANY WARRANTY; without even the implied warranty of
23  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24  * GNU General Public License for more details.
25 
26  * You should have received a copy of the GNU General Public License
27  * along with GHOST. If not, see http://www.gnu.org/licenses/.
28  */
29 
30 #pragma once
31 
32 #include <iostream>
33 #include <iomanip>
34 #include <cmath>
35 #include <limits>
36 #include <random>
37 #include <algorithm>
38 #include <vector>
39 #include <deque>
40 #include <chrono>
41 #include <memory>
42 #include <iterator>
43 #include <thread>
44 #include <future>
45 
46 #include "variable.hpp"
47 #include "constraint.hpp"
48 #include "objective.hpp"
49 #include "auxiliary_data.hpp"
50 #include "model.hpp"
51 #include "model_builder.hpp"
52 #include "options.hpp"
53 #include "search_unit.hpp"
54 
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"
59 
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"
64 
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"
68 
69 #include "algorithms/culprit_search_error_projection_heuristic.hpp"
70 
71 #include "macros.hpp"
72 
73 namespace ghost
74 {
109  template<typename ModelBuilderType> class Solver final
110  {
111  Model _model;
112  ModelBuilderType _model_builder; // Factory building the model
113 
114  int _number_variables; // Size of the vector of variables.
115  int _number_constraints; // Size of the vector of constraints.
116 
117  double _best_sat_error;
118  double _best_opt_cost;
119  double _cost_before_postprocess;
120 
121  // global statistics, cumulation of all threads stats.
122  int _restarts_total;
123  int _resets_total;
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;
129 
130  // stats of the winning thread
131  int _restarts;
132  int _resets;
133  int _local_moves;
134  int _search_iterations;
135  int _local_minimum;
136  int _plateau_moves;
137  int _plateau_local_minimum;
138 
139  std::string _variable_heuristic;
140  std::string _variable_candidates_heuristic;
141  std::string _value_heuristic;
142  std::string _error_projection_heuristic;
143 
144  // From search_unit_data
145  // Matrix to know which constraints contain a given variable
146  // matrix_var_ctr[ variable_id ] = { constraint_id_1, ..., constraint_id_k }
147  std::vector<std::vector<int> > _matrix_var_ctr;
148 
149  Options _options; // Options for the solver (see the struct Options).
150 
151  // AC3 algorithm for complete_search. This method is handling the filtering, and return filtered domains.
152  // The vector of vector 'domains' is passed by copy on purpose.
153  // The value of variable[ index_v ] has already been set before the call
154  std::vector< std::vector<int>> ac3_filtering( int index_v, std::vector< std::vector<int>> domains )
155  {
156  // queue of (constraint id, variable id)
157  std::deque<std::pair<int, int>> ac3queue;
158 
159  for( int constraint_id : _matrix_var_ctr[ index_v ] )
160  for( int variable_id : _model.constraints[ constraint_id ]->_variables_index )
161  {
162  if( variable_id <= index_v )
163  continue;
164 
165  ac3queue.push_back( std::make_pair( constraint_id, variable_id ) );
166  }
167 
168  std::vector<int> values_to_remove;
169  while( !ac3queue.empty() )
170  {
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] )
176  {
177  _model.variables[variable_id].set_value( value );
178  if( !has_support( constraint_id, variable_id, value, index_v, domains ) )
179  {
180  values_to_remove.push_back( value );
181  for( int c_id : _matrix_var_ctr[ variable_id ] )
182  {
183  if( c_id == constraint_id )
184  continue;
185 
186  for( int v_id : _model.constraints[ c_id ]->_variables_index )
187  {
188  if( v_id <= index_v || v_id == variable_id )
189  continue;
190 
191  if( std::find_if( ac3queue.begin(),
192  ac3queue.end(),
193  [&]( auto& elem ){ return elem.first == c_id && elem.second == v_id; } ) == ac3queue.end() )
194  {
195  ac3queue.push_back( std::make_pair( c_id, v_id ) );
196  }
197  }
198  }
199  }
200  }
201 
202  for( int value : values_to_remove )
203  domains[variable_id].erase( std::find( domains[variable_id].begin(), domains[variable_id].end(), value ) );
204 
205  // once a domain is empty, no need to go further
206  if( domains[variable_id].empty() )
207  return domains;
208  }
209 
210  return domains;
211  }
212 
213  // Method called by ac3_filtering, to compute if variable_id assigned to value has some support for the constraint
214  // constraint_id, but testing iteratively all combination of values for free variables until finding a local solution,
215  // or exhausting all possibilities. Return true if and only if a support exists.
216  // Values of variable[ index_v ] and variable[ variable_id ] have already been set before the call
217  bool has_support( int constraint_id, int variable_id, int value, int index_v, const std::vector< std::vector<int>>& domains )
218  {
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 );
223 
224  // Case where there are no free variables
225  if( constraint_scope.empty() )
226  return _model.constraints[ constraint_id ]->error() == 0.0;
227 
228  // From here, there are some free variables to assign
229  std::vector<int> indexes( constraint_scope.size() + 1, 0 );
230  int fake_index = static_cast<int>( indexes.size() ) - 1;
231 
232  while( indexes[ fake_index ] == 0 )
233  {
234  for( int i = 0 ; i < fake_index ; ++i )
235  {
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 );
239  }
240 
241  if( _model.constraints[ constraint_id ]->error() == 0.0 )
242  return true;
243  else
244  {
245  bool changed;
246  int index = 0;
247  do
248  {
249  changed = false;
250  ++indexes[ index ];
251  if( index < fake_index && indexes[ index ] >= static_cast<int>( domains[ constraint_scope[ index ] ].size() ) )
252  {
253  indexes[ index ] = 0;
254  changed = true;
255  ++index;
256  }
257  }
258  while( changed );
259  }
260  }
261 
262  return false;
263  }
264 
265  // Recursive call of complete_search. Search for all solutions of the problem instance.
266  // index_v is the index of the last variable assigned. Return the vector of some found solutions.
267  // The value of variable[ index_v ] has already been set before the call
268  std::vector<std::vector<int>> complete_search( int index_v, std::vector< std::vector<int>> domains )
269  {
270  // should never be called
271  if( index_v >= _model.variables.size() )
272  return std::vector<std::vector<int>>();
273 
274  std::vector< std::vector<int>> new_domains;
275  if( index_v > 0 )
276  {
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(); } );
279 
280  if( empty_domain != new_domains.cend() )
281  return std::vector<std::vector<int>>();
282  }
283  else
284  {
285  new_domains = domains; // already filtered
286  }
287 
288  int next_var = index_v + 1;
289  std::vector<std::vector<int>> solutions;
290  for( auto value : new_domains[next_var] )
291  {
292  _model.variables[next_var].set_value( value );
293 
294  // last variable
295  if( next_var == _model.variables.size() - 1 )
296  {
297  std::vector<int> solution;
298  for( auto& var : _model.variables )
299  solution.emplace_back( var.get_value() );
300 
301  solutions.emplace_back( solution );
302  }
303  else // not the last variable: recursive call
304  {
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(); } );
311  }
312  }
313 
314  return solutions;
315  }
316 
317  public:
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 ),
331  _resets_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 ),
337  _restarts( 0 ),
338  _resets( 0 ),
339  _local_moves( 0 ),
340  _search_iterations( 0 ),
341  _local_minimum( 0 ),
342  _plateau_moves( 0 ),
343  _plateau_local_minimum( 0 )
344  { }
345 
399  bool fast_search( double& final_cost,
400  std::vector<int>& final_solution,
401  double timeout,
402  Options& options )
403  {
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 );
409 
410  /*****************
411  * Initialization *
412  ******************/
413  // Only to get the number of variables and constraints
414  _model_builder.declare_variables();
415  _number_variables = _model_builder.get_number_variables();
416 
417  _options = options;
418 
419  if( _options.tabu_time_local_min < 0 )
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;
421  //_options.tabu_time_local_min = std::max( 2, _tabu_threshold ) );
422 
423  if( _options.tabu_time_selected < 0 )
424  _options.tabu_time_selected = 0;
425 
426  if( _options.percent_chance_escape_plateau < 0 || _options.percent_chance_escape_plateau > 100 )
427  _options.percent_chance_escape_plateau = 10;
428 
429  if( _options.reset_threshold < 0 )
430  _options.reset_threshold = _options.tabu_time_local_min;
431 // _options.reset_threshold = static_cast<int>( std::ceil( 1.5 * _options.reset_threshold ) );
432 // _options.reset_threshold = 2 * static_cast<int>( std::ceil( std::sqrt( _number_variables ) ) );
433 
434  if( _options.restart_threshold < 0 )
435  _options.restart_threshold = _number_variables;
436 
437  if( _options.number_variables_to_reset < 0 )
438  _options.number_variables_to_reset = std::max( 2, static_cast<int>( std::ceil( _number_variables * 0.1 ) ) ); // 10%
439 
440  if( _options.number_start_samplings < 0 )
441  _options.number_start_samplings = 10;
442 
443  double chrono_search;
444  double chrono_full_computation;
445 
446  // In case final_solution is not a vector of the correct size,
447  // ie, equals to the number of variables.
448  final_solution.resize( _number_variables );
449  bool solution_found = false;
450  bool is_sequential;
451  bool is_optimization;
452 
453 #if defined GHOST_DEBUG || defined GHOST_TRACE || defined GHOST_BENCH
454  // this is to make proper benchmarks/debugging with 1 thread.
455  is_sequential = !_options.parallel_runs;
456 #else
457  is_sequential = ( !_options.parallel_runs || _options.number_threads == 1 );
458 #endif
459 
460  // sequential runs
461  if( is_sequential )
462  {
463  SearchUnit search_unit( _model_builder.build_model(),
464  _options );
465 
466  is_optimization = search_unit.data.is_optimization;
467  std::future<bool> unit_future = search_unit.solution_found.get_future();
468 
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();
473 
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;
484 
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();
489 
490  _model = std::move( search_unit.transfer_model() );
491  }
492  else // call threads
493  {
494  std::vector<SearchUnit> units;
495  units.reserve( _options.number_threads );
496  std::vector<std::thread> unit_threads;
497 
498  for( int i = 0 ; i < _options.number_threads; ++i )
499  {
500  // Instantiate one model per thread
501  units.emplace_back( _model_builder.build_model(),
502  _options );
503  }
504 
505  is_optimization = units[0].data.is_optimization;
506 
507  std::vector<std::future<bool>> units_future;
508  std::vector<bool> units_terminated( _options.number_threads, false );
509 
510  start_search = std::chrono::steady_clock::now();
511 
512  for( int i = 0 ; i < _options.number_threads; ++i )
513  {
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() );
517  }
518 
519  int thread_number = 0;
520  int winning_thread = 0;
521  bool end_of_computation = false;
522  int number_timeouts = 0;
523 
524  while( !end_of_computation )
525  {
526  for( thread_number = 0 ; thread_number < _options.number_threads ; ++thread_number )
527  {
528  if( !units_terminated[ thread_number ] && units_future.at( thread_number ).wait_for( std::chrono::microseconds( 0 ) ) == std::future_status::ready )
529  {
530  if( is_optimization )
531  {
532  ++number_timeouts;
533  units_terminated[ thread_number ] = true;
534 
535  if( units_future.at( thread_number ).get() ) // equivalent to if( units.at( thread_number ).best_sat_error == 0.0 )
536  {
537  solution_found = true;
538  if( _best_opt_cost > units.at( thread_number ).data.best_opt_cost )
539  {
540  _best_opt_cost = units.at( thread_number ).data.best_opt_cost;
541  winning_thread = thread_number;
542  }
543  }
544 
545  if( number_timeouts >= _options.number_threads )
546  {
547  end_of_computation = true;
548  break;
549  }
550  }
551  else // then it is a satisfaction problem
552  {
553  if( units_future.at( thread_number ).get() )
554  {
555  solution_found = true;
556  units_terminated[ thread_number ] = true;
557  winning_thread = thread_number;
558  end_of_computation = true;
559  break;
560  }
561  else
562  {
563  ++number_timeouts;
564  units_terminated[ thread_number ] = true;
565  if( number_timeouts >= _options.number_threads )
566  {
567  end_of_computation = true;
568  break;
569  }
570  }
571  }
572  }
573  }
574  }
575 
576  elapsed_time = std::chrono::steady_clock::now() - start_search;
577  chrono_search = elapsed_time.count();
578 
579  // Collect all interesting data before terminating threads.
580  // Stats first...
581  for( int i = 0 ; i < _options.number_threads ; ++i )
582  {
583  units.at(i).stop_search();
584 
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;
592  }
593 
594  // ..then the most important: the best solution found so far.
595  if( solution_found )
596  {
597 #if defined GHOST_TRACE
598  std::cout << "Parallel run, thread number " << winning_thread << " has found a solution.\n";
599 #endif
600  _best_sat_error = units.at( winning_thread ).data.best_sat_error;
601  _best_opt_cost = units.at( winning_thread ).data.best_opt_cost;
602 
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;
610 
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();
615 
616  _model = std::move( units.at( winning_thread ).transfer_model() );
617  }
618  else
619  {
620 #if defined GHOST_TRACE
621  std::cout << "Parallel run, no solutions found.\n";
622 #endif
623  int best_non_solution = 0;
624  for( int i = 0 ; i < _options.number_threads ; ++i )
625  {
626  if( _best_sat_error > units.at( i ).data.best_sat_error )
627  {
628  best_non_solution = i;
629  _best_sat_error = units.at( i ).data.best_sat_error;
630  }
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 )
633  {
634  best_non_solution = i;
635  _best_opt_cost = units.at( i ).data.best_opt_cost;
636  }
637  }
638 
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;
646 
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();
651 
652  _model = std::move( units.at( best_non_solution ).transfer_model() );
653  }
654 
655  for( auto& thread: unit_threads )
656  {
657 #if defined GHOST_TRACE
658  std::cout << "Joining and terminating thread number " << thread.get_id() << "\n";
659 #endif
660  thread.join();
661  }
662  }
663 
664  if( solution_found && is_optimization )
665  {
666  _cost_before_postprocess = _best_opt_cost;
667 
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;
671  }
672 
673  if( is_optimization )
674  {
675  if( _model.objective->is_maximization() )
676  {
677  _best_opt_cost = -_best_opt_cost;
678  _cost_before_postprocess = -_cost_before_postprocess;
679  }
680 
681  final_cost = _best_opt_cost;
682  }
683  else
684  final_cost = _best_sat_error;
685 
686  std::transform( _model.variables.begin(),
687  _model.variables.end(),
688  final_solution.begin(),
689  [&](auto& var){ return var.get_value(); } );
690 
691  elapsed_time = std::chrono::steady_clock::now() - start_wall_clock;
692  chrono_full_computation = elapsed_time.count();
693 
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"
707  << "Percentage of chance to espace a plateau rather than exploring it: " << _options.percent_chance_escape_plateau << "%\n"
708  << _options.number_variables_to_reset << " variables are reset when " << _options.reset_threshold << " variables are frozen\n";
709  if( _options.restart_threshold > 0 )
710  std::cout << "Do a restart each time " << _options.restart_threshold << " resets are performed\n";
711  else
712  std::cout << "Never perform restarts\n";
713  std::cout << "############" << "\n";
714 
715  // Print solution
716  std::cout << _options.print->print_candidate( _model.variables ).str();
717 
718  std::cout << "\n";
719 
720  if( !is_optimization )
721  std::cout << "SATISFACTION run" << "\n";
722  else
723  {
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";
727  else
728  std::cout << _model.objective->get_name() << " must be minimized.\n";
729  }
730 
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";
741 
742  if( _options.parallel_runs )
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";
748 
749  if( is_optimization )
750  std::cout << "\nOptimization cost: " << _best_opt_cost << "\n";
751 
752  // If post-processing takes more than 1 microsecond, print details about it on the screen
753  // This is to avoid printing something with empty post-processing, taking usually less than 0.1 microsecond (tested on a Core i9 9900)
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";
757 
758  std::cout << "\n";
759 #endif
760 
761  return solution_found;
762  }
763 
777  bool fast_search( double& final_cost, std::vector<int>& final_solution, double timeout )
778  {
779  Options options;
780  return fast_search( final_cost, final_solution, timeout, options );
781  }
782 
803  bool fast_search( double& final_cost, std::vector<int>& final_solution, std::chrono::microseconds timeout, Options& options )
804  {
805  return fast_search( final_cost, final_solution, timeout.count(), options );
806  }
807 
824  bool fast_search( double& final_cost, std::vector<int>& final_solution, std::chrono::microseconds timeout )
825  {
826  Options options;
827  return fast_search( final_cost, final_solution, timeout, options );
828  }
829 
830 
858  bool complete_search( std::vector<double>& final_costs,
859  std::vector<std::vector<int>>& final_solutions,
860  Options& options )
861  {
862  // init data
863  bool solutions_exist = false;
864  _options = options;
865 
866  _model = _model_builder.build_model();
867 
868  std::vector< std::vector<int> > domains;
869  for( auto& var : _model.variables )
870  domains.emplace_back( var.get_full_domain() );
871 
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 );
877 
878  for( int value : domains[0] )
879  {
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(); } );
883 
884  if( empty_domain == new_domains.cend() )
885  {
886  std::vector<std::vector<int>> partial_solutions = complete_search( 0, new_domains );
887 
888  for( auto& solution : partial_solutions )
889  if( !solution.empty() )
890  {
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 );
896  }
897  }
898  }
899 
900  // need to reassigned the variables value to solutions one by one
901  //std::cout << _options.print->print_candidate( _model.variables ).str();
902  return solutions_exist;
903  }
904 
917  bool complete_search( std::vector<double>& final_costs, std::vector<std::vector<int>>& final_solutions )
918  {
919  Options options;
920  return complete_search( final_costs, final_solutions, options );
921  }
922 
929  inline std::vector<Variable> get_variables() { return _model.variables; }
930  };
931 }
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