QUBO++ Library with QUBO Solver APIs
Author: Koji Nakano, License: Non-commercial research and evaluation purposes without any guarantees.
factorization.cpp
Go to the documentation of this file.
1 
19 #include <boost/multiprecision/cpp_int.hpp>
20 #include <boost/multiprecision/miller_rabin.hpp>
21 #include <boost/program_options.hpp>
22 #include <chrono>
23 #include <iostream>
24 #include <string>
25 #include <thread>
26 
27 #include "qbpp.hpp"
28 #include "qbpp_abs2.hpp"
29 #include "qbpp_easy_solver.hpp"
30 #include "qbpp_grb.hpp"
31 #include "qbpp_misc.hpp"
32 #include "qbpp_multiplier.hpp"
33 
34 namespace qbpp {
35 namespace factorization {
36 
40 std::string toBinaryString(uint64_t num) {
41  if (num == 0) return "0";
42  std::string binary = "";
43  while (num > 0) {
44  binary = (num % 2 == 0 ? "0" : "1") + binary;
45  num /= 2;
46  }
47  return binary;
48 }
49 
55 uint64_t as_uint64(const qbpp::Sol &sol, const qbpp::Vector<qbpp::Var> &var,
56  bool fix_lsb) {
57  uint64_t result = 0;
58  for (int i = var.size() - 1; i >= 1; --i) {
59  result = (result << 1) + sol.get(var[i]);
60  }
61  if (fix_lsb) {
62  result = (result << 1) + 1;
63  } else {
64  result = (result << 1) + sol.get(var[0]);
65  }
66  return result;
67 }
68 
75 uint64_t as_uint64(const qbpp::SolHolder &sol_holder,
76  const qbpp::Vector<qbpp::Var> &var, bool fix_lsb) {
77  return as_uint64(sol_holder.copy_sol(), var, fix_lsb);
78 }
79 
80 bool is_prime(uint32_t num) {
81  return boost::multiprecision::miller_rabin_test(
83 }
84 
89 uint32_t rand_prime(int size) {
90  uint32_t num;
91  do {
92  num = qbpp::misc::RandomGenerator::gen(1 << (size - 2)) |
93  (1 << (size - 1)) | 1;
94  } while (is_prime(num) == false);
95  return num;
96 }
97 
101 
102 class SolHolder : public qbpp::SolHolder {
104  const bool factorization_mode = true;
106  const bool fix_lsb = false;
114  std::optional<int64_t> bound = std::nullopt;
116  mutable std::mutex mtx;
117 
118  public:
124  SolHolder(const qbpp::QuadModel &quad_model, bool fix_lsb,
127  : qbpp::SolHolder(quad_model),
128  fix_lsb(fix_lsb),
129  var_x(var_x),
130  var_y(var_y),
131  var_z(std::move(qbpp::Vector<qbpp::Var>{})) {}
132 
137  SolHolder(const qbpp::QuadModel &quad_model, bool fix_lsb,
139  : qbpp::SolHolder(quad_model),
140  factorization_mode(false),
141  fix_lsb(fix_lsb),
142  var_x(std::move(qbpp::Vector<qbpp::Var>{})),
143  var_y(std::move(qbpp::Vector<qbpp::Var>{})),
144  var_z(var_z) {}
145 
146  SolHolder(const SolHolder &sol_holder) = default;
147 
150  void set_bound(int64_t bound) { this->bound = bound; }
151 
154  std::optional<int64_t> get_bound() const { return bound; }
155 
158  std::string get_bound_str() const {
159  if (bound.has_value()) {
160  return std::to_string(bound.value());
161  } else {
162  return "N/A";
163  }
164  }
165 
172  std::optional<double> set_if_better(const qbpp::Sol &new_sol,
173  const std::string &solver) override {
174  std::lock_guard<std::mutex> lock(mtx);
175  std::optional<double> tts = qbpp::SolHolder::set_if_better(new_sol, solver);
176  if (tts.has_value()) {
177  std::cerr << std::left << std::setw(4) << get_solver() << " "
178  << std::fixed << std::setprecision(3) << tts.value()
179  << "s Energy = " << new_sol.get_energy()
180  << " Bound = " << get_bound_str() << " : ";
181  if (factorization_mode) {
182  std::cerr << toBinaryString(as_uint64(new_sol, var_x, fix_lsb)) << " * "
183  << toBinaryString(as_uint64(new_sol, var_y, fix_lsb)) << " = "
184  << toBinaryString(as_uint64(new_sol, var_x, fix_lsb) *
185  as_uint64(new_sol, var_y, fix_lsb))
186  << " (" << as_uint64(new_sol, var_x, fix_lsb) << " * "
187  << as_uint64(new_sol, var_y, fix_lsb) << " = "
188  << as_uint64(new_sol, var_x, fix_lsb) *
189  as_uint64(new_sol, var_y, fix_lsb)
190  << ")" << std::endl;
191  } else {
192  std::cout << toBinaryString(as_uint64(new_sol, var_z, fix_lsb)) << " ("
193  << as_uint64(new_sol, var_z, fix_lsb) << ")" << std::endl;
194  }
195  }
196  return tts;
197  }
198 };
199 
203  // SolHolder &sol_holder;
204  const std::shared_ptr<SolHolder> sol_holder_ptr_;
205 
206  public:
212  std::shared_ptr<SolHolder> sol_holder_ptr)
213  : qbpp_abs2::Callback(quad_model), sol_holder_ptr_(sol_holder_ptr) {}
214 
215  void callback(const std::string &event) override {
216  if (event == "init") {
217  // Enable the callback for every new best solution.
219  } else if (event == "new") {
220  auto sol = get_sol();
221  sol_holder_ptr_->set_if_better(sol, "ABS2");
222  auto hint = sol_holder_ptr_->get_if_better(sol);
223  if (hint.has_value()) set_hint(hint.value());
224  }
225  }
226 }; // namespace factorization
227 
232  // SolHolder &sol_holder;
233  std::shared_ptr<SolHolder> sol_holder_ptr_;
236 
237  public:
243  std::shared_ptr<SolHolder> sol_holder_ptr)
244  : qbpp_grb::Callback(quad_model), sol_holder_ptr_(sol_holder_ptr) {}
245 
249  void callback() override {
250  if (where == GRB_CB_MIPSOL) {
251  qbpp_grb::Sol sol = get_sol();
252  sol_holder_ptr_->set_if_better(sol, "GRB");
253  }
254  if (where == GRB_CB_MIP) {
255  sol_holder_ptr_->set_bound(
256  std::round(getDoubleInfoPublic(GRB_CB_MIP_OBJBND)));
257  auto hint = sol_holder_ptr_->get_if_better(
258  std::round(getDoubleInfoPublic(GRB_CB_MIP_OBJBST)));
259  if (hint.has_value()) {
260  for (qbpp::vindex_t i = 0; i < quad_model.var_count(); ++i) {
261  setSolution(quad_model.get_grb_var(i), hint.value().get(i));
262  }
263  useSolution();
264  }
265  }
266  if (where == GRB_CB_MIPNODE) {
267  sol_holder_ptr_->set_bound(
268  std::round(getDoubleInfoPublic(GRB_CB_MIPNODE_OBJBND)));
269  auto hint = sol_holder_ptr_->get_if_better(
270  std::round(getDoubleInfoPublic(GRB_CB_MIPNODE_OBJBST)));
271  if (hint.has_value()) {
272  for (qbpp::vindex_t i = 0; i < quad_model.var_count(); ++i) {
273  setSolution(quad_model.get_grb_var(i), hint.value().get(i));
274  }
275  useSolution();
276  }
277  }
279  }
280 };
281 
282 } // namespace factorization
283 } // namespace qbpp
284 
285 using namespace qbpp::factorization;
286 namespace po = boost::program_options;
287 
295 int main(int argc, char *argv[]) {
296  // clang-format off
297  po::options_description desc(
298  "Factorization/Multiplication z = x * y using QUBO solvers");
299  desc.add_options()("help,h", "produce help message")
300  ("multiplication,m", "solve multiplication problem (default: factorization)")
301  ("x,x", po::value<uint32_t>(),"set value of x")
302  ("y,y", po::value<uint32_t>(), "set value of y")
303  ("random,r", po::value<uint32_t>(), "set bit size of random prime numbers for x and y (default: 32)")
304  ("time,t", po::value<uint32_t>(), "set time limit in seconds")
305  ("fix,f", "fix the LSBs of x, y, and z")
306  ("seed,s", po::value<uint32_t>(), "set random seed")
307  ("abs2,a", "use ABS2 GPU QUBO solver")
308  ("gurobi,g", "use Gurobi optimizer")
309  ("easy,e", "use QUBO++ Easy solver");
310  // clang-format on
311 
312  po::variables_map vm;
313  try {
314  po::store(po::parse_command_line(argc, argv, desc), vm);
315  } catch (const std::exception &e) {
316  std::cout << "Wrong arguments. Please use -h/--help option to see the "
317  "usage.\n";
318  return 1;
319  }
320  po::notify(vm);
321 
322  if (vm.count("help")) {
323  std::cout << desc << std::endl;
324  return 0;
325  }
326 
327  // Set the random seed for deterministic behavior if seed is provided.
328  if (vm.count("seed")) {
329  qbpp::misc::RandomGenerator::set_seed(vm["seed"].as<uint32_t>());
330  }
331 
332  bool fix_lsb = vm.count("fix");
333  bool use_abs2 = vm.count("abs2");
334  bool use_grb = vm.count("gurobi");
335  bool use_easy = vm.count("easy");
336  if (!use_abs2 && !use_grb && !use_easy) {
337  use_abs2 = true;
338  use_grb = true;
339  use_easy = true;
340  }
341 
342  // Set the time limit to 60 seconds by default.
343  int time_limit;
344  if (vm.count("time")) {
345  time_limit = vm["time"].as<uint32_t>();
346  } else {
347  time_limit = 60;
348  }
349 
350  // Set the problem type to factorization or multiplication.
351  bool factorization_mode = true;
352  if (vm.count("multiplication")) factorization_mode = false;
353 
354  // The input values X and Y are randomly generated by default.
355  uint64_t X = 0, Y = 0;
356  if (vm.count("x") && vm.count("y")) {
357  X = vm["x"].as<uint32_t>();
358  Y = vm["y"].as<uint32_t>();
359  if (is_prime(X) == false) {
360  std::cerr << "x must be a prime number." << std::endl;
361  return 1;
362  }
363  if (is_prime(Y) == false) {
364  std::cerr << "y must be a prime number." << std::endl;
365  return 1;
366  }
367  } else {
368  int size = 32;
369  if (vm.count("random")) size = vm["random"].as<uint32_t>();
370  do {
371  X = rand_prime(size);
372  Y = rand_prime(size);
373  } while (X == Y);
374  }
375 
376  // The product Z of X and Y is calculated.
377  uint64_t Z = X * Y;
378  // The number of bits of X, Y, and Z are calculated.
379  uint32_t Xsize = 32 - __builtin_clz(X);
380  uint32_t Ysize = 32 - __builtin_clz(Y);
381  uint32_t Zsize = Xsize + Ysize;
382 
383  // Create variables x, y, and z.
384  auto var_x = qbpp::var("x", Xsize);
385  auto var_y = qbpp::var("y", Ysize);
386  auto var_z = qbpp::var("z", Zsize);
387 
388  // Stores the input values of z
389  // qbpp::VarIntMap x_var_map, y_var_map, z_var_map;
390  // qbpp::VarIntMap expected_var_map;
391  qbpp::MapList x_var_map, y_var_map, z_var_map;
392  qbpp::MapList opt_sol;
393 
394  for (uint32_t i = 0; i < Xsize; ++i) {
395  x_var_map.push_back({var_x[i], (X >> i) & 1});
396  }
397  for (uint32_t i = 0; i < Ysize; ++i) {
398  y_var_map.push_back({var_y[i], (Y >> i) & 1});
399  }
400 
401  for (uint32_t i = 0; i < Zsize; ++i) {
402  z_var_map.push_back({var_z[i], (Z >> i) & 1});
403  }
404 
405  opt_sol.insert(opt_sol.end(), x_var_map.begin(), x_var_map.end());
406  opt_sol.insert(opt_sol.end(), y_var_map.begin(), y_var_map.end());
407  opt_sol.insert(opt_sol.end(), z_var_map.begin(), z_var_map.end());
408 
409  // Returns QUBO quad_model for multiplier of var_z = var_x * var_y
410  qbpp::Expr expr = multiplier(var_x, var_y, var_z, opt_sol);
411 
412  // Assignment by assign performed and then simplified as a binary
413  // expression.
414  if (factorization_mode) {
415  expr.replace(z_var_map);
416  } else {
417  expr.replace(x_var_map);
418  expr.replace(y_var_map);
419  }
420 
421  if (fix_lsb) {
422  expr.replace({{var_x[0], 1}, {var_y[0], 1}, {var_z[0], 1}});
423  }
424 
426 
427  // Create a QUBO quad_model from the expression.
428  auto quad_model = qbpp::QuadModel(expr);
429 
430  // Create a holder to store the best solution by ABS2 and Gurobi.
431  std::shared_ptr<SolHolder> sol_holder_ptr;
432  if (factorization_mode) {
433  sol_holder_ptr =
434  std::make_shared<SolHolder>(quad_model, fix_lsb, var_x, var_y);
435  } else {
436  sol_holder_ptr = std::make_shared<SolHolder>(quad_model, fix_lsb, var_z);
437  }
438  std::cout << "QUBO : Variables = " << quad_model.var_count()
439  << " Linear terms = " << quad_model.term_count(1)
440  << " Quad terms = " << quad_model.term_count(2) << std::endl;
441  std::cout << "Target : " << toBinaryString(X) << " * " << toBinaryString(Y)
442  << " = " << toBinaryString(Z) << " (" << X << " * " << Y << " = "
443  << Z << ")\n";
444 
445  std::thread easy_thread;
446  std::thread abs2_thread;
447  std::thread grb_thread;
448 
449  std::shared_ptr<qbpp::easy_solver::EasySolver> easy_solver_ptr;
450 
451  if (use_easy) {
452  easy_solver_ptr = std::make_shared<qbpp::easy_solver::EasySolver>(
453  quad_model, sol_holder_ptr);
454  easy_solver_ptr->set_time_limit(time_limit);
455  easy_solver_ptr->set_target_energy(0);
456  easy_thread = std::thread([&easy_solver_ptr, &sol_holder_ptr]() {
457  auto sol = easy_solver_ptr->search();
458  sol_holder_ptr->set_if_better(sol, "EASY");
459  });
460  }
461 
462  std::shared_ptr<qbpp_abs2::Solver> abs2_solver_ptr;
463  std::shared_ptr<qbpp_abs2::QuadModel> abs2_quad_model_ptr;
464  std::shared_ptr<qbpp_abs2::Param> abs2_param_ptr;
465  std::shared_ptr<ABS2Callback> abs2_callback_ptr;
466 
467  if (use_abs2) {
468  // Initialize ABS2 solver
469  abs2_solver_ptr = std::make_shared<qbpp_abs2::Solver>();
470  // Create a quad_model for ABS2 solver from
471  // quad_model
472  abs2_quad_model_ptr = std::make_shared<qbpp_abs2::QuadModel>(quad_model);
473  // Create a callback function for ABS2 solver.
474  abs2_callback_ptr =
475  std::make_shared<ABS2Callback>(*abs2_quad_model_ptr, sol_holder_ptr);
476  // Create a parameters object to store solver
477  // parameters.
478  abs2_param_ptr = std::make_shared<qbpp_abs2::Param>();
479  // Set a time limit
480  abs2_param_ptr->set_time_limit(time_limit);
481  // Set the target energy to 0.
482  abs2_param_ptr->set_target_energy(0);
483  // Set the callback function
484  abs2_param_ptr->set(*abs2_callback_ptr);
485  abs2_thread = std::thread([&abs2_solver_ptr, &abs2_quad_model_ptr,
486  &abs2_param_ptr, &sol_holder_ptr]() {
487  auto sol = (*abs2_solver_ptr)(*abs2_quad_model_ptr, *abs2_param_ptr);
488  sol_holder_ptr->set_if_better(sol, "ABS2");
489  });
490  }
491 
492  std::shared_ptr<qbpp_grb::QuadModel> grb_quad_model_ptr;
493  std::shared_ptr<GRB_Callback> grb_callback_ptr;
494  if (use_grb) {
495  // Create a Gurobi quad_model from quad_model.
496  grb_quad_model_ptr = std::make_shared<qbpp_grb::QuadModel>(quad_model);
497  // Set the time limit for the Gurobi solver.
498  grb_quad_model_ptr->set_time_limit(time_limit);
499  // Create a callback function for Gurobi solver.
500  grb_callback_ptr =
501  std::make_shared<GRB_Callback>(*grb_quad_model_ptr, sol_holder_ptr);
502  // Set the target energy to 0.
503  grb_callback_ptr->set_target_energy(0);
504  // Set the callback function
505  grb_quad_model_ptr->set(*grb_callback_ptr);
506  grb_thread = std::thread([&grb_quad_model_ptr, &sol_holder_ptr]() {
507  auto sol = grb_quad_model_ptr->optimize();
508  sol_holder_ptr->set_if_better(sol, "GRB");
509  });
510  }
511 
512  if (use_easy) easy_thread.join();
513  if (use_abs2) abs2_thread.join();
514  if (use_grb) grb_thread.join();
515 
516  std::cout << "Target : " << toBinaryString(X) << " * " << toBinaryString(Y)
517  << " = " << toBinaryString(Z) << " (" << X << " * " << Y << " = "
518  << Z << ")\n";
519 }
void set(const std::string &operation)
Set the operation to the ABS2 Callback.
Callback()
Constructor for creating a callback object.
Expr & simplify_as_binary()
Definition: qbpp.hpp:1956
Expr & replace(const MapList &map_list)
Definition: qbpp.hpp:2045
vindex_t var_count() const
Definition: qbpp.hpp:1154
Sol copy_sol() const
Definition: qbpp.hpp:1641
virtual std::optional< double > set_if_better(const qbpp::Sol &new_sol, const std::string &solver="")
Definition: qbpp.hpp:1600
std::string get_solver() const
Definition: qbpp.hpp:1655
energy_t get_energy() const
Definition: qbpp.hpp:1545
var_val_t get(vindex_t index) const
Definition: qbpp.hpp:1500
size_t size() const
Definition: qbpp.hpp:540
Class to define ABS2 callback function for factorization.
ABS2Callback(const qbpp_abs2::QuadModel &quad_model, std::shared_ptr< SolHolder > sol_holder_ptr)
Construct a new ABS2Callback object for factorization.
const std::shared_ptr< SolHolder > sol_holder_ptr_
The solution holder.
void callback(const std::string &event) override
The default callback function for ABS2 QUBO solver.
Class to define Gurobi callback function for factorization.
void callback() override
callback function for Gurobi optimizer.
std::shared_ptr< SolHolder > sol_holder_ptr_
The solution holder for Gurobi.
GRB_Callback(qbpp_grb::QuadModel &quad_model, std::shared_ptr< SolHolder > sol_holder_ptr)
Target energy to stop the Gurobi optimizer. std::optional<qbpp::energy_t> target_energy = std::nullop...
Solution holder to store the best solution of factorization.
std::string get_bound_str() const
Gets the bound value of the best solution as a string.
const qbpp::Vector< qbpp::Var > & var_x
The vector of Var.
std::optional< double > set_if_better(const qbpp::Sol &new_sol, const std::string &solver) override
Sets new_sol as the best solution if it is better than the current best solution.
SolHolder(const qbpp::QuadModel &quad_model, bool fix_lsb, const qbpp::Vector< qbpp::Var > &var_x, const qbpp::Vector< qbpp::Var > &var_y)
Constructor with a quad_model.
std::mutex mtx
Mutex for thread safety in print function.
const bool factorization_mode
True if factorization, false if multiplication.
const qbpp::Vector< qbpp::Var > & var_z
The vector of Var.
std::optional< int64_t > bound
The bound of the best solution found by Gurobi.
const qbpp::Vector< qbpp::Var > & var_y
The vector of Var.
std::optional< int64_t > get_bound() const
Gets the bound value of the best solution.
void set_bound(int64_t bound)
Sets the bound value of the best solution.
SolHolder(const SolHolder &sol_holder)=default
SolHolder(const qbpp::QuadModel &quad_model, bool fix_lsb, const qbpp::Vector< qbpp::Var > &var_z)
Constructor with a quad_model.
const bool fix_lsb
True if the MSBs and LSBs of x, y, and z are fixed.
static void set_seed(uint32_t seed=1)
Definition: qbpp_misc.hpp:41
static uint64_t gen()
Definition: qbpp_misc.hpp:45
A class for defining the ABS2 callback function.
Definition: qbpp_abs2.hpp:204
const QuadModel quad_model
QuadModel object created by QUBO++ library.
Definition: qbpp_abs2.hpp:207
A class for storing both the qbpp::QuadModel and abs2::Model.
Definition: qbpp_abs2.hpp:76
Class to manage a callback function called by Gurobi Optimizer.
Definition: qbpp_grb.hpp:149
void abort_if_target_energy(qbpp::energy_t energy)
Abort the optimization process if the target energy is achieved.
Definition: qbpp_grb.hpp:201
const QuadModel quad_model
QUBO model.
Definition: qbpp_grb.hpp:152
double getDoubleInfoPublic(int what)
Calls GetDoubleInfo() of GRBCallback.
Definition: qbpp_grb.hpp:210
Sol get_sol()
Get the solution obtained by Gurobi Optimizer.
Definition: qbpp_grb.hpp:274
Callback(const QuadModel &quad_model)
Constructor: a new Callback object.
Definition: qbpp_grb.hpp:169
Class to store a QUBO model using Gurobi Optimizer through QUBO++ library.
Definition: qbpp_grb.hpp:42
GRBVar get_grb_var(int index) const
Get the Gurobi variable from the index.
Definition: qbpp_grb.hpp:92
Class to store a solution of a QUBO model using Gurobi Optimizer.
Definition: qbpp_grb.hpp:120
int main(int argc, char *argv[])
Solves the factorization problem using ABS2 QUBO Solver and Gurobi optimizer from QUBO++ library.
Sol get_sol() const
Get the solution from the ABS2 solver.
Definition: qbpp_abs2.hpp:222
void set_hint(const Sol &hint)
Provide a hint solution to the ABS2 solver.
Definition: qbpp_abs2.hpp:227
Namespace for factorization using QUBO++ library.
Expr multiplier(const qbpp::Vector< T > &x, const qbpp::Vector< U > &y, const qbpp::Vector< Var > &z, MapList &opt_sol)
Function to generate QUBO expression for multiplier.
uint32_t rand_prime(int size)
Generates a random prime number of the specified size.
bool is_prime(uint32_t num)
uint64_t as_uint64(const qbpp::Sol &sol, const qbpp::Vector< qbpp::Var > &var, bool fix_lsb)
Converts a vector of Var in a solution to a 64-bit unsigned integer.
std::string toBinaryString(uint64_t num)
Converts a number to a binary string.
Namespace to use ABS2 QUBO solver from QUBO++ library.
Definition: qbpp_abs2.hpp:16
Namespace to use Gurobi optimizer from QUBO++ library.
Definition: qbpp_grb.hpp:25
Generates a QUBO Expression for the Graph Coloring Problem using QUBO++ library.
Definition: qbpp.hpp:53
Var var(const std::string &var_str)
Definition: qbpp.hpp:2172
boost::multiprecision::uint128_t uint128_t
Definition: qbpp.hpp:105
uint32_t vindex_t
Definition: qbpp.hpp:122
Expr expr()
Definition: qbpp.hpp:2318
std::list< std::pair< std::variant< Var, VarInt >, Expr > > MapList
Definition: qbpp.hpp:134
QUBO++, a C++ library for generating expressions for binary and spin variables.
QUBO++ interface to call ABS2 GPU QUBO solver.
Easy QUBO Solver for solving QUBO problems.
QUBO++ interface to call Gurobi Optimizer.
A miscellaneous library used for sample programs of the QUBO++ library.
Generates QUBO expression for binary multipliers using QUBO++ library.