QUBO++ Library with QUBO Solver APIs
Author: Koji Nakano, License: Non-commercial research and evaluation purposes without any guarantees.
qbpp_easy_solver.hpp
Go to the documentation of this file.
1 
18 #ifndef QBPP_EASY_SOLVER_HPP
19 #define QBPP_EASY_SOLVER_HPP
20 
21 #include <tbb/blocked_range.h>
22 #include <tbb/parallel_for.h>
23 
24 #include <boost/circular_buffer.hpp>
25 #include <limits>
26 #include <random>
27 #include <set>
28 #include <thread>
29 #include <vector>
30 
31 #include "qbpp.hpp"
32 #include "qbpp_misc.hpp"
33 
34 namespace qbpp {
35 
36 namespace easy_solver {
37 
38 
39 class RandomSet;
40 class MinSet;
41 class RandomMinSet;
42 class Tabu;
43 class SolDelta;
44 class TabuSolDelta;
45 class PosMinSolDelta;
46 class EasySolver;
47 
48 
49 class RandomSet {
50  std::vector<vindex_t> position_;
51 
52  std::vector<vindex_t> variables_;
53 
54  public:
55  explicit RandomSet(vindex_t size) : position_(size, vindex_limit) {
56  variables_.reserve(size);
57  }
58 
59  vindex_t var_count() const {
60  return static_cast<vindex_t>(variables_.size());
61  }
62 
63  void insert(vindex_t index) {
64  if (position_[index] != vindex_limit) {
65  throw std::runtime_error(THROW_MESSAGE("RandomSet: Insert variable (",
66  index, ") already in set"));
67  }
68  variables_.push_back(index);
69  position_[index] = static_cast<vindex_t>(variables_.size() - 1);
70  }
71 
72  void swap(vindex_t pos1, vindex_t pos2) {
73  if (pos1 == pos2) return;
74  std::swap(variables_[pos1], variables_[pos2]);
75  position_[variables_[pos1]] = pos1;
76  position_[variables_[pos2]] = pos2;
77  }
78 
79  void erase(vindex_t index) {
80  if (position_[index] == vindex_limit) {
81  throw std::runtime_error(
82  THROW_MESSAGE("RandomSet: Erase variable (", index, ") not in set"));
83  }
84  swap(position_[index], var_count() - 1);
85  variables_.pop_back();
86  position_[index] = vindex_limit;
87  }
88 
89  bool has(vindex_t index) const { return position_[index] != vindex_limit; }
90 
93  }
94 
95  void print(const std::string &prefix = "") const {
96  std::cout << prefix << " Size = " << var_count() << " : ";
97  for (vindex_t i = 0; i < var_count(); ++i) {
98  std::cout << " " << variables_[i];
99  }
100  std::cout << std::endl;
101  }
102 };
103 
104 class MinSet {
105  using ValIndexMap = std::set<std::pair<energy_t, vindex_t>>;
106 
108 
109  public:
110  MinSet() = default;
111 
112  vindex_t var_count() const { return static_cast<vindex_t>(set_.size()); }
113 
114  void insert(vindex_t index, energy_t delta) {
115  auto tuple_val = std::make_pair(delta, index);
116  auto result = set_.insert(tuple_val);
117  if (!result.second) {
118  throw std::runtime_error(THROW_MESSAGE("MinSet: Insert variable (", index,
119  ") already in set"));
120  }
121  }
122 
123  void erase(vindex_t index, energy_t delta) {
124  auto pair_val = std::make_pair(delta, index);
125  auto result = set_.erase(pair_val);
126  if (result == 0) {
127  throw std::runtime_error(
128  THROW_MESSAGE("MinSet: Erase variable (", index, ") not in set"));
129  }
130  }
131 
132  vindex_t get_first() const { return (*set_.begin()).second; }
133 
134  void print(const std::string &prefix) const {
135  std::cout << prefix;
136  for (auto &[val, i] : set_) {
137  std::cout << "(" << i << "," << val << ")";
138  }
139  std::cout << std::endl;
140  }
141 };
142 
146 
147  public:
149 
150  vindex_t var_count() const {
151  return static_cast<vindex_t>(min_set_.var_count());
152  }
153 
154  void insert(vindex_t index, energy_t delta) {
155  min_set_.insert(index, delta);
156  random_set_.insert(index);
157  }
158 
159  void erase(vindex_t index, energy_t delta) {
160  min_set_.erase(index, delta);
161  random_set_.erase(index);
162  }
163 
164  bool has(vindex_t index) const { return random_set_.has(index); }
165 
167 
168  vindex_t get_first() const { return min_set_.get_first(); }
169 
170  void print(const std::string &prefix) const {
171  min_set_.print(prefix + " MIN:");
172  random_set_.print(prefix + "RANDOM:");
173  }
174 };
175 
176 class Tabu {
178 
179  boost::circular_buffer<vindex_t> tabu_list;
180 
181  public:
182  vindex_t tabu_size() const { return static_cast<vindex_t>(tabu_list.size()); }
183 
185  : variables_(size), tabu_list(tabu_size) {};
186 
187  bool has(vindex_t index) const { return variables_.get(index); }
188 
189  void insert(vindex_t index) {
190  if (has(index)) {
191  auto it = std::find(tabu_list.begin(), tabu_list.end(), index);
192  tabu_list.erase(it);
193  } else if (tabu_list.full()) {
194  variables_.set(tabu_list.front(), false);
195  tabu_list.pop_front();
196  }
197  tabu_list.push_back(index);
198  variables_.set(index, true);
199  }
200 
202  vindex_t index;
203  do {
205  } while (has(index));
206  return index;
207  }
208 
209  void print() {
210  std::cout << "Tabu list:";
211  for (const auto &i : tabu_list) {
212  std::cout << " " << i;
213  }
214  std::cout << std::endl;
215  }
216 };
217 
218 class SolDelta : public Sol {
219  protected:
221 
222  std::vector<energy_t> delta_;
223 
225 
226  std::shared_ptr<qbpp::SolHolder> sol_holder_ptr_;
227 
228  public:
229  explicit SolDelta(const EasySolver &easy_solver);
230 
231  virtual ~SolDelta() = default;
232 
233  energy_t get_delta(vindex_t i) const { return delta_[i]; }
234 
235  virtual void flip(vindex_t index);
236 
237  std::optional<double> set_if_better(const std::string &solver_name);
238 
239  vindex_t neg_count() const { return neg_set_.var_count(); }
240 
242 
243  vindex_t neg_min() const { return neg_set_.get_first(); }
244 
245  virtual void before_delta_updated(
246  [[maybe_unused]] vindex_t flip_index,
247  [[maybe_unused]] vindex_t update_delta_index) {}
248 
249  virtual void after_delta_updated(
250  [[maybe_unused]] vindex_t flip_index,
251  [[maybe_unused]] vindex_t update_delta_index) {}
252 
253  void greedy();
254 
255  void random_flip(size_t iteration);
256 
257  void move_to(qbpp::Sol destination);
258 };
259 
260 class TabuSolDelta : public SolDelta {
261  protected:
263 
265 
266  public:
267  TabuSolDelta(const EasySolver &easy_solver, vindex_t tabu_size)
268  : SolDelta(easy_solver),
269  tabu_size_(tabu_size),
270  tabu_(var_count(), tabu_size) {}
271 
272  virtual ~TabuSolDelta() = default;
273 
274  void flip(vindex_t index) override {
275  SolDelta::flip(index);
276  tabu_.insert(index);
277  }
278 
279  bool tabu_has(vindex_t index) const { return tabu_.has(index); }
280 
282 };
283 
284 class PosMinSolDelta : public TabuSolDelta {
286 
287  public:
288  explicit PosMinSolDelta(const EasySolver &easy_solver);
289 
290  virtual ~PosMinSolDelta() = default;
291 
292  vindex_t pos_count() const { return pos_set_.var_count(); }
293 
294  vindex_t pos_min() const { return pos_set_.get_first(); }
295 
297  if (delta_[k] > 0) {
298  pos_set_.erase(k, delta_[k]);
299  }
300  }
301 
302  void after_delta_updated(vindex_t , vindex_t k) override {
303  if (delta_[k] > 0) {
304  pos_set_.insert(k, delta_[k]);
305  }
306  }
307 
308  void search(size_t iteration);
309 
310  void print() const {
311  for (vindex_t i = 0; i < var_count(); ++i) {
312  std::cout << "(" << i << "," << delta_[i] << ")";
313  }
314  std::cout << std::endl;
315  neg_set_.print("NEG:");
316  pos_set_.print("POS:");
317  }
318 };
319 
320 class EasySolver {
321  protected:
323 
324  std::optional<uint32_t> time_limit_;
325 
326  size_t thread_count_{std::thread::hardware_concurrency() < 4
327  ? 4
328  : std::thread::hardware_concurrency()};
329 
330  std::optional<energy_t> target_energy_;
331 
332  const std::shared_ptr<qbpp::SolHolder> sol_holder_ptr_;
333 
334  mutable std::mutex callback_mutex_;
335 
337 
338  void single_search(size_t thread_id);
339 
340  double start_time_;
341 
342  mutable std::optional<energy_t> prev_energy_ = std::nullopt;
343 
344  public:
345 
346  explicit EasySolver(const qbpp::QuadModel &quad_model,
347  std::shared_ptr<qbpp::SolHolder> sol_holder_ptr = nullptr)
348  : quad_model_(quad_model),
349  sol_holder_ptr_(sol_holder_ptr
350  ? sol_holder_ptr
351  : std::make_shared<qbpp::SolHolder>(quad_model_)) {}
352 
353 
354  virtual ~EasySolver() = default;
355 
356  const qbpp::QuadModel &get_quad_model() const { return quad_model_; }
357 
358  const std::optional<uint32_t> get_time_limit() const { return time_limit_; }
359 
360  const std::optional<energy_t> get_target_energy() const {
361  return target_energy_;
362  }
363 
364  const std::shared_ptr<qbpp::SolHolder> get_sol_holder_ptr() const {
365  return sol_holder_ptr_;
366  }
367 
368  vindex_t var_count() const { return quad_model_.var_count(); }
369 
370  void set_time_limit(uint32_t limit) { time_limit_ = limit; }
371 
372  void set_target_energy(energy_t energy) { target_energy_ = energy; }
373 
374  void set_thread_count(unsigned int count) { thread_count_ = count; }
375 
376  size_t get_thread_count() { return thread_count_; }
377 
378  qbpp::Sol get_sol() const { return sol_holder_ptr_->get_sol(); }
379 
380  double get_tts() const { return sol_holder_ptr_->get_tts(); }
381 
383 
384  virtual void callback(const qbpp::Sol &sol, double tts) const {
385  std::lock_guard<std::mutex> lock(callback_mutex_);
387  if (!prev_energy_.has_value() ||
388  sol.get_energy() < prev_energy_.value()) {
389  std::cout << "TTS = " << std::fixed << std::setprecision(3)
390  << std::setfill('0') << tts
391  << "s Energy = " << sol.get_energy() << std::endl;
392  prev_energy_ = sol.get_energy();
393  }
394  }
395  }
396 
397  qbpp::Sol search();
398 
399 };
400 
401 
402 inline SolDelta::SolDelta(const EasySolver &easy_solver)
403  : Sol(easy_solver.get_quad_model()),
404  easy_solver_(easy_solver),
405  delta_(var_count()),
406  neg_set_(var_count()),
407  sol_holder_ptr_(easy_solver.get_sol_holder_ptr()) {
408  for (vindex_t i = 0; i < var_count(); ++i) {
409  delta_[i] = quad_model_.get_linear(i);
410  }
411 }
412 
413 inline void SolDelta::flip(vindex_t index) {
414  if (index >= var_count()) {
415  throw std::out_of_range(
416  THROW_MESSAGE("Flip index (", index, ") is out of range."));
417  }
418 
419  for (vindex_t j = 0; j < quad_model_.get_degree(index); ++j) {
420  auto [k, coeff] = quad_model_.get_quadratic(index, j);
421 
422  if (delta_[k] < 0) {
423  neg_set_.erase(k, delta_[k]);
424  }
425  before_delta_updated(index, k);
426 
427  delta_[k] +=
428  static_cast<energy_t>((2 * get(index) - 1) * (2 * get(k) - 1) * coeff);
429 
430  if (delta_[k] < 0) {
431  neg_set_.insert(k, delta_[k]);
432  }
433  after_delta_updated(index, k);
434  }
435 
436  if (delta_[index] < 0) {
437  neg_set_.erase(index, delta_[index]);
438  }
439  before_delta_updated(index, index);
440 
441  delta_[index] = -delta_[index];
442 
443  if (delta_[index] < 0) {
444  neg_set_.insert(index, delta_[index]);
445  }
446 
447  after_delta_updated(index, index);
448 
449  if (energy_.has_value()) {
450  *energy_ -= delta_[index];
451  } else {
452  energy_ = comp_energy();
453  }
454 
455  bit_vector_.flip(index);
456 }
457 
458 inline void SolDelta::greedy() {
459  while (neg_count() > 0) {
460  vindex_t min_index = neg_min();
461  flip(min_index);
462  }
463  set_if_better("Easy");
464 }
465 
466 inline void SolDelta::random_flip(size_t iteration) {
467  for (vindex_t i = 0; i < iteration; ++i) {
469  flip(flip_index);
470  set_if_better("Easy");
471  }
472 }
473 
474 inline void SolDelta::move_to(qbpp::Sol destination) {
475  MinSet to_be_flipped;
476  for (vindex_t i = 0; i < var_count(); ++i) {
477  if (get(i) != destination.get(i)) {
478  to_be_flipped.insert(i, delta_[i]);
479  }
480  }
481  while (to_be_flipped.var_count() > 0) {
482  vindex_t min_index = to_be_flipped.get_first();
483  for (vindex_t j = 0; j < quad_model_.get_degree(min_index); ++j) {
484  auto pair = quad_model_.get_quadratic(min_index, j);
485  if (get(pair.first) != destination.get(pair.first)) {
486  to_be_flipped.erase(pair.first, delta_[pair.first]);
487  }
488  }
489  to_be_flipped.erase(min_index, delta_[min_index]);
490  flip(min_index);
491  for (vindex_t j = 0; j < quad_model_.get_degree(min_index); ++j) {
492  auto pair = quad_model_.get_quadratic(min_index, j);
493  if (get(pair.first) != destination.get(pair.first))
494  to_be_flipped.insert(pair.first, delta_[pair.first]);
495  }
496  set_if_better("Easy");
497  }
498 }
499 
500 inline std::optional<double> SolDelta::set_if_better(
501  const std::string &solver_name) {
502  std::optional<double> tts =
503  sol_holder_ptr_->set_if_better(*this, solver_name);
504  if (tts.has_value()) {
505  easy_solver_.callback(*this, tts.value());
506  }
507  return tts;
508 }
509 
510 
511 inline PosMinSolDelta::PosMinSolDelta(const EasySolver &easy_solver)
512  : TabuSolDelta(easy_solver,
513  std::min(easy_solver.get_quad_model().var_count() / 2,
514  static_cast<vindex_t>(5))) {
515  for (vindex_t i = 0; i < var_count(); ++i) {
516  if (delta_[i] < 0) {
517  neg_set_.insert(i, delta_[i]);
518  } else if (delta_[i] > 0) {
519  pos_set_.insert(i, delta_[i]);
520  }
521  }
522 }
523 
524 inline void PosMinSolDelta::search(size_t iteration) {
525  greedy();
526  for (vindex_t i = 0; i < iteration; ++i) {
527  std::optional<vindex_t> flip_index = std::nullopt;
528  vindex_t min_index = neg_min();
529  if (neg_count() >= 1 &&
530  get_energy() + get_delta(min_index) < sol_holder_ptr_->get_energy()) {
531  flip_index = min_index;
532  } else {
533  for (vindex_t j = 0; j < tabu_size_ * 2; ++j) {
534  vindex_t candidate = vindex_limit;
535  if (pos_count() == 0 || neg_count() == 0) {
537  } else if (qbpp::misc::RandomGenerator::gen(neg_count() + 1) == 0) {
538  candidate = pos_min();
539  } else {
540  candidate = neg_random();
541  }
542  if (candidate == vindex_limit) {
543  throw std::runtime_error(THROW_MESSAGE("Unexpected error."));
544  }
545  if (!tabu_.has(candidate)) {
546  flip_index = candidate;
547  break;
548  }
549  }
550  if (!flip_index.has_value()) {
551  flip_index = tabu_.non_tabu_random();
552  }
553  }
554  flip(flip_index.value());
555  tabu_.insert(flip_index.value());
556  set_if_better("Easy");
557  if (easy_solver_.get_target_energy().has_value() &&
559  break;
560  }
561  greedy();
562 }
563 
564 inline void EasySolver::single_search(size_t thread_id) {
565  const size_t min_flip_count = 100;
566  PosMinSolDelta pos_min_sol_delta(*this);
567  size_t max_flip_count = (var_count() / 2);
568  if (thread_count_ > 1) {
569  double k = std::pow(static_cast<double>(max_flip_count) / min_flip_count,
570  1.0 / static_cast<double>(thread_count_ - 1));
571  max_flip_count =
572  static_cast<size_t>(min_flip_count * std::pow(k, thread_id));
573  if (max_flip_count < min_flip_count) max_flip_count = min_flip_count;
574  }
575  energy_t prev_energy = sol_holder_ptr_->get_energy();
576  size_t random_flip_count = 1;
577  while (!time_limit_.has_value() ||
578  qbpp::get_time() - start_time_ < time_limit_.value()) {
579  pos_min_sol_delta.move_to(sol_holder_ptr_->get_sol());
580  pos_min_sol_delta.random_flip(random_flip_count);
581  pos_min_sol_delta.search(max_flip_count);
582  if (target_energy_.has_value() &&
583  sol_holder_ptr_->get_energy() <= target_energy_.value())
584  break;
585  if (prev_energy == sol_holder_ptr_->get_energy()) {
586  random_flip_count = (random_flip_count + 1) % (max_flip_count / 2);
587  if (random_flip_count == 0) random_flip_count = 1;
588  } else {
589  random_flip_count = 1;
590  }
591  prev_energy = sol_holder_ptr_->get_energy();
592  }
593 }
594 
596  if (quad_model_.term_count() == 0) return sol_holder_ptr_->get_sol();
598  tbb::parallel_for(size_t(0), static_cast<size_t>(thread_count_),
599  [this](size_t thread_id) { single_search(thread_id); });
600  return sol_holder_ptr_->get_sol();
601 }
602 
603 }
604 }
605 
606 #endif
vindex_t var_count() const
Definition: qbpp.hpp:1154
size_t term_count(vindex_t deg) const
Definition: qbpp.hpp:1258
const std::vector< std::vector< std::pair< vindex_t, coeff_t > > > & get_quadratic() const
Definition: qbpp.hpp:1245
const std::vector< coeff_t > & get_linear() const
Definition: qbpp.hpp:1241
const std::vector< vindex_t > & get_degree() const
Definition: qbpp.hpp:1243
vindex_t var_count() const
Definition: qbpp.hpp:1556
const QuadModel quad_model_
Definition: qbpp.hpp:1444
energy_t get_energy() const
Definition: qbpp.hpp:1545
std::optional< energy_t > energy_
Definition: qbpp.hpp:1448
energy_t comp_energy() const
Definition: qbpp.hpp:2809
impl::BitVector bit_vector_
Definition: qbpp.hpp:1446
var_val_t get(vindex_t index) const
Definition: qbpp.hpp:1500
const std::optional< energy_t > get_target_energy() const
const std::shared_ptr< qbpp::SolHolder > sol_holder_ptr_
const qbpp::QuadModel quad_model_
EasySolver(const qbpp::QuadModel &quad_model, std::shared_ptr< qbpp::SolHolder > sol_holder_ptr=nullptr)
const std::shared_ptr< qbpp::SolHolder > get_sol_holder_ptr() const
void single_search(size_t thread_id)
std::optional< energy_t > target_energy_
virtual ~EasySolver()=default
std::optional< energy_t > prev_energy_
const qbpp::QuadModel & get_quad_model() const
void set_thread_count(unsigned int count)
std::optional< uint32_t > time_limit_
void set_target_energy(energy_t energy)
const std::optional< uint32_t > get_time_limit() const
virtual void callback(const qbpp::Sol &sol, double tts) const
void set_time_limit(uint32_t limit)
std::set< std::pair< energy_t, vindex_t > > ValIndexMap
void erase(vindex_t index, energy_t delta)
void insert(vindex_t index, energy_t delta)
void print(const std::string &prefix) const
void before_delta_updated(vindex_t, vindex_t k) override
void after_delta_updated(vindex_t, vindex_t k) override
PosMinSolDelta(const EasySolver &easy_solver)
void erase(vindex_t index, energy_t delta)
void print(const std::string &prefix) const
void insert(vindex_t index, energy_t delta)
bool has(vindex_t index) const
std::vector< vindex_t > position_
void print(const std::string &prefix="") const
std::vector< vindex_t > variables_
void swap(vindex_t pos1, vindex_t pos2)
bool has(vindex_t index) const
std::shared_ptr< qbpp::SolHolder > sol_holder_ptr_
SolDelta(const EasySolver &easy_solver)
energy_t get_delta(vindex_t i) const
virtual void after_delta_updated([[maybe_unused]] vindex_t flip_index, [[maybe_unused]] vindex_t update_delta_index)
virtual ~SolDelta()=default
virtual void flip(vindex_t index)
std::vector< energy_t > delta_
virtual void before_delta_updated([[maybe_unused]] vindex_t flip_index, [[maybe_unused]] vindex_t update_delta_index)
std::optional< double > set_if_better(const std::string &solver_name)
void random_flip(size_t iteration)
void move_to(qbpp::Sol destination)
virtual ~TabuSolDelta()=default
bool tabu_has(vindex_t index) const
void flip(vindex_t index) override
TabuSolDelta(const EasySolver &easy_solver, vindex_t tabu_size)
void insert(vindex_t index)
Tabu(vindex_t size, vindex_t tabu_size)
bool has(vindex_t index) const
boost::circular_buffer< vindex_t > tabu_list
bool get(vindex_t index) const
Definition: qbpp.hpp:1381
void flip(vindex_t index)
Definition: qbpp.hpp:1398
vindex_t size() const
Definition: qbpp.hpp:1377
void set(vindex_t index, bool value)
Definition: qbpp.hpp:1388
static uint64_t gen()
Definition: qbpp_misc.hpp:45
Generates a QUBO Expression for the Graph Coloring Problem using QUBO++ library.
Definition: qbpp.hpp:53
double get_time()
Definition: qbpp.hpp:3815
uint32_t vindex_t
Definition: qbpp.hpp:122
ENERGY_TYPE energy_t
Definition: qbpp.hpp:130
const vindex_t vindex_limit
Definition: qbpp.hpp:124
QUBO++, a C++ library for generating expressions for binary and spin variables.
#define THROW_MESSAGE(...)
Definition: qbpp.hpp:86
A miscellaneous library used for sample programs of the QUBO++ library.