MISolver.cc 23.9 KB
Newer Older
Henrik Zimmer's avatar
Henrik Zimmer committed
1
2
/*===========================================================================*\
 *                                                                           *
Henrik Zimmer's avatar
Henrik Zimmer committed
3
 *                               CoMISo                                      *
Henrik Zimmer's avatar
Henrik Zimmer committed
4
 *      Copyright (C) 2008-2009 by Computer Graphics Group, RWTH Aachen      *
Henrik Zimmer's avatar
Henrik Zimmer committed
5
 *                           www.rwth-graphics.de                            *
Henrik Zimmer's avatar
Henrik Zimmer committed
6
 *                                                                           *
7
 *---------------------------------------------------------------------------*
Henrik Zimmer's avatar
Henrik Zimmer committed
8
 *  This file is part of CoMISo.                                             *
Henrik Zimmer's avatar
Henrik Zimmer committed
9
 *                                                                           *
Henrik Zimmer's avatar
Henrik Zimmer committed
10
11
12
13
 *  CoMISo is free software: you can redistribute it and/or modify           *
 *  it under the terms of the GNU General Public License as published by     *
 *  the Free Software Foundation, either version 3 of the License, or        *
 *  (at your option) any later version.                                      *
Henrik Zimmer's avatar
Henrik Zimmer committed
14
15
16
17
 *                                                                           *
 *  CoMISo is distributed in the hope that it will be useful,                *
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
Henrik Zimmer's avatar
Henrik Zimmer committed
18
 *  GNU General Public License for more details.                             *
Henrik Zimmer's avatar
Henrik Zimmer committed
19
 *                                                                           *
Henrik Zimmer's avatar
Henrik Zimmer committed
20
21
 *  You should have received a copy of the GNU General Public License        *
 *  along with CoMISo.  If not, see <http://www.gnu.org/licenses/>.          *
Henrik Zimmer's avatar
Henrik Zimmer committed
22
 *                                                                           *
23
\*===========================================================================*/
Henrik Zimmer's avatar
Henrik Zimmer committed
24

David Bommes's avatar
David Bommes committed
25
#include <CoMISo/Config/config.hh>
26
#include "MISolver.hh"
27
#include "CoMISo/Utils/CoMISoError.hh"
28

29
#if (COMISO_QT_AVAILABLE)
30
#include <CoMISo/QtWidgets/MISolverDialogUI.hh>
31
32
#endif

33
34
35
36
37
#if COMISO_CPLEX_AVAILABLE
#include <ilcplex/ilocplex.h>
ILOSTLBEGIN
#endif

David Bommes's avatar
David Bommes committed
38
#if COMISO_GUROBI_AVAILABLE
39
#include <gurobi_c++.h>
David Bommes's avatar
David Bommes committed
40
41
#endif

42
43
44
#include <CoMISo/Utils/gmm.hh>

#include <Base/Debug/DebTime.hh>
45
#include <Base/Utils/StopWatch.hh>
David Bommes's avatar
David Bommes committed
46

47
#include <queue>
48
49
#include <float.h>

50
#define ROUND(x) ((x) < 0 ? int((x) - 0.5) : int((x) + 0.5))
David Bommes's avatar
David Bommes committed
51

52
53
54
55
namespace COMISO
{
namespace
{
56

57
58
59
60
61
62
63
class RoundingQueue : protected std::priority_queue<std::pair<double, int>>
{
public:
  RoundingQueue(const double _threshold) : threshold_(_threshold), cur_sum_(0.0)
  {
    c.reserve(128);
  }
64

65
66
67
68
69
  void clear()
  {
    c.clear();
    cur_sum_ = 0.0;
  }
70

71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
  void add(const int _id, const double _rd_val)
  {
    if (empty() || cur_sum_ + _rd_val <= threshold_)
    { // empty? -> always add one element
      emplace(_rd_val, _id);
      cur_sum_ += _rd_val;
      return;
    }
    if (top().first > _rd_val) // replace the last element if worse
    {
      cur_sum_ -= top().first;
      pop();
      emplace(_rd_val, _id);
      cur_sum_ += _rd_val;
    }
  }

  void get_ids(std::vector<int>& _ids)
  {
    _ids.clear();
    _ids.reserve(size());
    std::sort_heap(c.begin(), c.end());
    for (auto s_it = c.begin(); s_it != c.end(); ++s_it)
      _ids.push_back(s_it->second);
  }

private:
  const double threshold_;
  double cur_sum_;
};

// gmm Column and ColumnIterator of CSC matrix
typedef gmm::linalg_traits<MISolver::CSCMatrix>::const_sub_col_type Col;
typedef gmm::linalg_traits<Col>::const_iterator ColIter;

} // namespace
107
108

// Constructor
109
MISolver::MISolver()
110
111
112
{
  // default parameters
  initial_full_solution_ = true;
113
114
  iter_full_solution_ = true;
  final_full_solution_ = true;
115

116
117
  direct_rounding_ = false;
  no_rounding_ = false;
David Bommes's avatar
David Bommes committed
118
  multiple_rounding_ = true;
119
120
  gurobi_rounding_ = false;
  cplex_rounding_ = false;
121

David Bommes's avatar
David Bommes committed
122
  max_local_iters_ = 100000;
123
  max_local_error_ = 1e-3;
124
125
  max_cg_iters_ = 50;
  max_cg_error_ = 1e-3;
David Bommes's avatar
David Bommes committed
126
127
128

  multiple_rounding_threshold_ = 0.5;

David Bommes's avatar
David Bommes committed
129
130
  gurobi_max_time_ = 60;

131
132
  noisy_ = 0;
  stats_ = true;
133
134

  use_constraint_reordering_ = true;
135
136
}

137
138
//-----------------------------------------------------------------------------

139
140
void MISolver::solve(
    CSCMatrix& _A, Vecd& _x, Vecd& _rhs, Veci& _to_round, bool _fixed_order)
141
{
142
  DEB_enter_func;
143

144
145
146
  DEB_out(6, "# integer    variables: "
                 << _to_round.size() << "\n# continuous variables: "
                 << _x.size() - _to_round.size() << "\n");
147

148
  // nothing to solve?
149
150
  if (gmm::mat_ncols(_A) == 0 || gmm::mat_nrows(_A) == 0)
    return;
151

152
  if (gurobi_rounding_)
153
    solve_gurobi(_A, _x, _rhs, _to_round);
154
  else if (cplex_rounding_)
155
    solve_cplex(_A, _x, _rhs, _to_round);
156
157
158
159
160
161
  else if (no_rounding_ || _to_round.size() == 0)
    solve_no_rounding(_A, _x, _rhs);
  else if (direct_rounding_)
    solve_direct_rounding(_A, _x, _rhs, _to_round);
  else if (multiple_rounding_)
    solve_multiple_rounding(_A, _x, _rhs, _to_round);
162
  else
163
    solve_iterative(_A, _x, _rhs, _to_round, _fixed_order);
164
165
166
167
168
}

//-----------------------------------------------------------------------------

// inline function...
169
void MISolver::solve_cplex(CSCMatrix& _A, Vecd& _x, Vecd& _rhs, Veci& _to_round)
170
171
{
  DEB_enter_func;
172
  DEB_out(2, "gurobi_max_time_: " << gurobi_max_time_ << "\n");
173
174
175
176
177

#if COMISO_CPLEX_AVAILABLE

  // get round-indices in set
  std::set<int> to_round;
178
  for (unsigned int i = 0; i < _to_round.size(); ++i)
179
180
    to_round.insert(_to_round[i]);

181
182
  try
  {
183
184
185
186
187
188
189
190
191
192
193

    IloEnv env_;
    IloModel model(env_);

    // set time limite
    //    model.getEnv().set(GRB_DoubleParam_TimeLimit, gurobi_max_time_);

    unsigned int n = _rhs.size();

    // 1. allocate variables
    std::vector<IloNumVar> vars;
194
195
196
    for (unsigned int i = 0; i < n; ++i)
      if (to_round.count(i))
        vars.push_back(IloNumVar(env_, -IloIntMax, IloIntMax, IloNumVar::Int));
197
      else
198
199
        vars.push_back(
            IloNumVar(env_, -IloInfinity, IloInfinity, IloNumVar::Float));
200
201
202
203
204
205

    // 2. setup_energy

    // build objective function from linear system E = x^tAx - 2x^t*rhs
    IloExpr objective(env_);

206
207
    for (unsigned int i = 0; i < _A.nc; ++i)
      for (unsigned int j = _A.jc[i]; j < _A.jc[i + 1]; ++j)
208
      {
209
        objective += _A.pr[j] * vars[_A.ir[j]] * vars[i];
210
      }
211
212
    for (unsigned int i = 0; i < n; ++i)
      objective -= 2 * _rhs[i] * vars[i];
213
214
215
216
217
218
219
220
221
222

    // ToDo: objective correction!!!

    //    _A.jc[c+1]
    //    _A.pr[write]
    //    _A.ir[write]
    //    _A.nc
    //    _A.nr

    // minimize
223
    model.add(IloMinimize(env_, objective));
224
225
226
227
228

    // 4. solve
    IloCplex cplex(model);
    cplex.setParam(IloCplex::TiLim, gurobi_max_time_);

229
230
231
232
233
234
235
236
    //    // set parameters comparable to CoMISo
    //    {
    //      cplex.setParam(IloCplex::MIPSearch  , 1);  // Traditional
    //      Branch-and-Cut cplex.setParam(IloCplex::NodeSel    , 0);  //
    //      Depth-First cplex.setParam(IloCplex::VarSel     , -1);  // closest
    //      to integer cplex.setParam(IloCplex::MIPEmphasis, 1);  // concentrate
    //      on feasibility
    //    }
237
238
239
240
241

    cplex.solve();

    // 5. store result
    _x.resize(n);
242
    for (unsigned int i = 0; i < n; ++i)
243
244
      _x[i] = cplex.getValue(vars[i]);

245
    DEB_out(2, "CPLEX objective: " << cplex.getObjValue() << "\n");
246
247
248
  }
  catch (IloException& e)
  {
249
    PROGRESS_RESUME_ABORT; // resume a processed abort request
250
    DEB_warning(2, "CPLEX Concert exception caught: " << e.getMessage())
251
252
253
  }
  catch (...)
  {
254
    PROGRESS_RESUME_ABORT; // resume a processed abort request
255
    DEB_warning(1, "CPLEX Unknown exception caught")
256
257
258
259
260
261
262
  }

#else
  DEB_out(1, "CPLEX solver is not available, please install it...\n")
#endif
}

263
264
//-----------------------------------------------------------------------------

265
void MISolver::solve_no_rounding(CSCMatrix& _A, Vecd& _x, Vecd& _rhs)
David Bommes's avatar
David Bommes committed
266
{
267
268
  COMISO_THROW_if(
      !direct_solver_.calc_system_gmm(_A), UNSPECIFIED_EIGEN_FAILURE);
269
  COMISO_THROW_if(!direct_solver_.solve(_x, _rhs), UNSPECIFIED_EIGEN_FAILURE);
David Bommes's avatar
David Bommes committed
270
271
272
273
}

//-----------------------------------------------------------------------------

274
void MISolver::resolve(Vecd& _x, Vecd& _rhs) { direct_solver_.solve(_x, _rhs); }
275
276
277

//-----------------------------------------------------------------------------

278
279
void MISolver::solve_direct_rounding(
    CSCMatrix& _A, Vecd& _x, Vecd& _rhs, Veci& _to_round)
David Bommes's avatar
David Bommes committed
280
{
281
  DEB_enter_func Veci to_round(_to_round);
David Bommes's avatar
David Bommes committed
282
283
284
285
  // copy to round vector and make it unique
  std::sort(to_round.begin(), to_round.end());
  Veci::iterator last_unique;
  last_unique = std::unique(to_round.begin(), to_round.end());
Max Lyon's avatar
Max Lyon committed
286
  size_t r = (size_t)(last_unique - to_round.begin());
287
  to_round.resize(r);
David Bommes's avatar
David Bommes committed
288
289
290

  // initalize old indices
  Veci old_idx(_rhs.size());
291
  for (unsigned int i = 0; i < old_idx.size(); ++i)
David Bommes's avatar
David Bommes committed
292
    old_idx[i] = i;
293
294
  direct_solver_.calc_system_gmm(_A);
  direct_solver_.solve(_x, _rhs);
David Bommes's avatar
David Bommes committed
295

296
297
  // check solver performance (only for testing!!!)
  {
298
    Base::StopWatch sw;
299

300
    // hack
David Bommes's avatar
David Bommes committed
301
302
    const bool enable_performance_test = false;

303
    // performance comparison code
304
305
#if (COMISO_SUITESPARSE_SPQR_AVAILABLE)
    if (enable_performance_test)
306
307
308
309
    {
      sw.start();
      COMISO::SparseQRSolver spqr;
      spqr.calc_system_gmm(_A);
310
      std::cerr << "SparseQR factor took: " << sw.stop() / 1000.0 << "s\n";
311
312
      Vecd x2(_x);
      sw.start();
313
314
      spqr.solve(x2, _rhs);
      std::cerr << "SparseQR solve took: " << sw.stop() / 1000.0 << "s\n";
315
      Vecd res(_x);
316
317
318
      gmm::add(_x, gmm::scaled(x2, -1.0), res);
      std::cerr << "DIFFERENCE IN RESULT: " << gmm::vect_norm2(res)
                << std::endl;
319
    }
David Bommes's avatar
changed    
David Bommes committed
320
#endif
321
322

    // performance comparison code
323
324
#if (COMISO_SUITESPARSE_AVAILABLE)
    if (enable_performance_test)
325
326
327
328
    {
      sw.start();
      COMISO::UMFPACKSolver umf;
      umf.calc_system_gmm(_A);
329
      std::cerr << "UMFPack factor took: " << sw.stop() / 1000.0 << "s\n";
330
331
      Vecd x3(_x);
      sw.start();
332
333
      umf.solve(x3, _rhs);
      std::cerr << "UMFPack solve took: " << sw.stop() / 1000.0 << "s\n";
334
      Vecd res2(_x);
335
336
337
      gmm::add(_x, gmm::scaled(x3, -1.0), res2);
      std::cerr << "UMFPACK DIFFERENCE IN RESULT: " << gmm::vect_norm2(res2)
                << std::endl;
338
339
340
    }

    // performance comparison code
341
    if (enable_performance_test)
342
343
344
345
    {
      sw.start();
      COMISO::CholmodSolver chol;
      chol.calc_system_gmm(_A);
346
      std::cerr << "Choldmod factor took: " << sw.stop() / 1000.0 << "s\n";
347
348
      Vecd x4(_x);
      sw.start();
349
350
      chol.solve(x4, _rhs);
      std::cerr << "Choldmod solve took: " << sw.stop() / 1000.0 << "s\n";
351
      Vecd res(_x);
352
353
354
      gmm::add(_x, gmm::scaled(x4, -1.0), res);
      std::cerr << "DIFFERENCE IN RESULT: " << gmm::vect_norm2(res)
                << std::endl;
355
    }
356
357
#endif

358
#if (COMISO_EIGEN3_AVAILABLE)
359
    // performance comparison code
360
    if (enable_performance_test)
361
362
363
364
    {
      sw.start();
      COMISO::EigenLDLTSolver ldlt;
      ldlt.calc_system_gmm(_A);
365
366
      DEB_out(2, "Eigen LDLT factor took: " << sw.stop() / 1000.0 << "s\n")
          Vecd x5(_x);
367
      sw.start();
368
369
370
371
372
      ldlt.solve(x5, _rhs);
      DEB_out(2, "Eigen LDLT solve took: " << sw.stop() / 1000.0 << "s\n")
          Vecd res(_x);
      gmm::add(_x, gmm::scaled(x5, -1.0), res);
      DEB_warning(2, "DIFFERENCE IN RESULT: " << gmm::vect_norm2(res))
373
374
    }
#endif
375
  }
David Bommes's avatar
David Bommes committed
376
377
378

  // round and eliminate variables
  Vecui elim_i;
379
380
  Vecd elim_v;
  for (unsigned int i = 0; i < to_round.size(); ++i)
David Bommes's avatar
David Bommes committed
381
382
383
384
385
386
387
388
  {
    _x[to_round[i]] = ROUND(_x[to_round[i]]);
    elim_i.push_back(to_round[i]);
    elim_v.push_back(_x[to_round[i]]);
    // update old idx
    old_idx[to_round[i]] = -1;
  }

389
390
  Veci::iterator new_end = std::remove(old_idx.begin(), old_idx.end(), -1);
  old_idx.resize(new_end - old_idx.begin());
David Bommes's avatar
David Bommes committed
391
392
  // eliminate vars from linear system
  Vecd xr(_x);
393
  COMISO_GMM::eliminate_csc_vars2(elim_i, elim_v, _A, xr, _rhs);
David Bommes's avatar
David Bommes committed
394

395
  // std::cerr << "size A: " << gmm::mat_nrows(_A) << " " << gmm::mat_ncols(_A)
David Bommes's avatar
David Bommes committed
396
397
398
399
400
  // 	    << std::endl;
  // std::cerr << "size x  : " << xr.size() << std::endl;
  // std::cerr << "size rhs: " << _rhs.size() << std::endl;

  // final full solution
401
  if (gmm::mat_ncols(_A) > 0)
David Bommes's avatar
David Bommes committed
402
  {
403
404
    //    direct_solver_.update_system_gmm(_A);
    direct_solver_.calc_system_gmm(_A);
405
    direct_solver_.solve(xr, _rhs);
David Bommes's avatar
David Bommes committed
406
407
408
  }

  // store solution values to result vector
409
410
  for (unsigned int i = 0; i < old_idx.size(); ++i)
    _x[old_idx[i]] = xr[i];
David Bommes's avatar
David Bommes committed
411
412
413
414
}

//-----------------------------------------------------------------------------

415
416
void MISolver::solve_iterative(
    CSCMatrix& _A, Vecd& _x, Vecd& _rhs, Veci& _to_round, bool _fixed_order)
David Bommes's avatar
David Bommes committed
417
{
418
  DEB_enter_func;
David Bommes's avatar
David Bommes committed
419
  // StopWatch
420
  Base::StopWatch sw;
David Bommes's avatar
David Bommes committed
421
422
  double time_search_next_integer = 0;

423
  // some statistics
David Bommes's avatar
David Bommes committed
424
  n_local_ = 0;
425
426
  n_cg_ = 0;
  n_full_ = 0;
David Bommes's avatar
David Bommes committed
427
428
429

  // reset cholmod step flag
  cholmod_step_done_ = false;
430
431
432

  Veci to_round(_to_round);
  // if the order is not fixed, uniquify the indices
433
  if (!_fixed_order)
434
435
436
437
438
  {
    // copy to round vector and make it unique
    std::sort(to_round.begin(), to_round.end());
    Veci::iterator last_unique;
    last_unique = std::unique(to_round.begin(), to_round.end());
Max Lyon's avatar
Max Lyon committed
439
    size_t r = (size_t)(last_unique - to_round.begin());
440
    to_round.resize(r);
441
442
443
444
  }

  // initalize old indices
  Veci old_idx(_rhs.size());
445
  for (unsigned int i = 0; i < old_idx.size(); ++i)
446
447
    old_idx[i] = i;

448
  if (initial_full_solution_)
449
  {
450
451
    DEB_out_if(noisy_ > 2, 2, "initial full solution\n")
        direct_solver_.calc_system_gmm(_A);
452
    direct_solver_.solve(_x, _rhs);
453

David Bommes's avatar
David Bommes committed
454
    cholmod_step_done_ = true;
455

David Bommes's avatar
David Bommes committed
456
457
    ++n_full_;
  }
458
459
460
461
462
463
464
465

  // neighbors for local optimization
  Vecui neigh_i;

  // Vector for reduced solution
  Vecd xr(_x);

  // loop until solution computed
466
  for (unsigned int i = 0; i < to_round.size(); ++i)
467
  {
468
469
470
471
    DEB_out_if(noisy_ > 0, 2,
        "Integer DOF's left: " << to_round.size() - (i + 1)
                               << " ") DEB_out_if(noisy_ > 1, 1,
        "residuum_norm: " << COMISO_GMM::residuum_norm(_A, xr, _rhs) << "\n");
472
473
474
475

    // index to eliminate
    unsigned int i_best = 0;

David Bommes's avatar
David Bommes committed
476
477
478
    // position in round vector
    unsigned int tr_best = 0;

479
    if (_fixed_order) // if order is fixed, simply get next index from to_round
480
481
482
    {
      i_best = to_round[i];
    }
483
    else // else search for best rounding candidate
484
    {
David Bommes's avatar
David Bommes committed
485
      sw.start();
486
      // find index yielding smallest rounding error
487
488
      double r_best = FLT_MAX;
      for (unsigned int j = 0; j < to_round.size(); ++j)
489
      {
490
        if (to_round[j] != -1)
491
492
        {
          int cur_idx = to_round[j];
493
494
          double rnd_error = fabs(ROUND(xr[cur_idx]) - xr[cur_idx]);
          if (rnd_error < r_best)
495
          {
496
497
498
            i_best = cur_idx;
            r_best = rnd_error;
            tr_best = j;
499
500
501
          }
        }
      }
David Bommes's avatar
David Bommes committed
502
      time_search_next_integer += sw.stop();
503
504
505
506
    }

    // store rounded value
    double rnd_x = ROUND(xr[i_best]);
507
    _x[old_idx[i_best]] = rnd_x;
508
509
510
511

    // compute neighbors
    neigh_i.clear();
    Col col = gmm::mat_const_col(_A, i_best);
512
513
514
515
516
    ColIter it = gmm::vect_const_begin(col);
    ColIter ite = gmm::vect_const_end(col);
    for (; it != ite; ++it)
    {
      if (it.index() != i_best)
Andreas Fabri's avatar
Andreas Fabri committed
517
        neigh_i.push_back(static_cast<int>(it.index()));
518
    }
519
    // eliminate var
David Bommes's avatar
David Bommes committed
520
521
    COMISO_GMM::fix_var_csc_symmetric(i_best, rnd_x, _A, xr, _rhs);
    to_round[tr_best] = -1;
522

David Bommes's avatar
David Bommes committed
523
524
    // 3-stage update of solution w.r.t. roundings
    // local GS / CG / SparseCholesky
525
    update_solution(_A, xr, _rhs, neigh_i);
David Bommes's avatar
David Bommes committed
526
  }
527

David Bommes's avatar
David Bommes committed
528
  // final full solution?
529
  if (final_full_solution_)
David Bommes's avatar
David Bommes committed
530
  {
531
    DEB_line_if(noisy_ > 2, 2, "final full solution");
532

533
    if (gmm::mat_ncols(_A) > 0)
534
    {
535
536
      if (cholmod_step_done_)
        direct_solver_.update_system_gmm(_A);
David Bommes's avatar
David Bommes committed
537
      else
538
        direct_solver_.calc_system_gmm(_A);
539

540
      direct_solver_.solve(xr, _rhs);
David Bommes's avatar
David Bommes committed
541
      ++n_full_;
542
    }
David Bommes's avatar
David Bommes committed
543
544
545
  }

  // store solution values to result vector
546
547
  for (unsigned int i = 0; i < old_idx.size(); ++i)
    _x[old_idx[i]] = xr[i];
David Bommes's avatar
David Bommes committed
548
549

  // output statistics
550
551
552
553
554
555
556
557
  DEB_out_if(stats_, 2,
      " *** Statistics of MiSo Solver ***"
          << "\n Number of CG    iterations  = " << n_cg_
          << "\n Number of LOCAL iterations  = " << n_local_
          << "\n Number of FULL  iterations  = " << n_full_
          << "\n Number of ROUNDING          = " << _to_round.size()
          << "\n time searching next integer = "
          << time_search_next_integer / 1000.0 << "s\n\n");
David Bommes's avatar
David Bommes committed
558
}
559

David Bommes's avatar
David Bommes committed
560
561
//-----------------------------------------------------------------------------

562
563
void MISolver::update_solution(
    const CSCMatrix& _A, Vecd& _x, const Vecd& _rhs, const Vecui& _neigh_i)
David Bommes's avatar
David Bommes committed
564
{
565
566
  DEB_enter_func;
  bool converged = false; // set to not converged
David Bommes's avatar
David Bommes committed
567

568
  if (max_local_iters_ > 0) // compute new solution
David Bommes's avatar
David Bommes committed
569
  {
570
    DEB_out(6, "use local iteration ");
David Bommes's avatar
David Bommes committed
571

572
    int n_its = max_local_iters_;
David Bommes's avatar
David Bommes committed
573
    double tolerance = max_local_error_;
574
575
    converged =
        siter_.gauss_seidel_local(_A, _x, _rhs, _neigh_i, n_its, tolerance);
David Bommes's avatar
David Bommes committed
576
577
578
579
580

    ++n_local_;
  }

  // conjugate gradient
581
  if (!converged && max_cg_iters_ > 0)
David Bommes's avatar
David Bommes committed
582
  {
583
    DEB_out(6, ", cg ");
David Bommes's avatar
David Bommes committed
584
585
586

    int max_cg_iters = max_cg_iters_;
    double tolerance = max_cg_error_;
587
588
    converged =
        siter_.conjugate_gradient(_A, _x, _rhs, max_cg_iters, tolerance);
David Bommes's avatar
David Bommes committed
589

590
591
592
    DEB_out(6, "( converged " << converged << " "
                              << " iters " << max_cg_iters << " "
                              << " res_norm " << tolerance << "\n");
David Bommes's avatar
David Bommes committed
593
594
595
    ++n_cg_;
  }

596
  if (!converged && iter_full_solution_ && gmm::mat_ncols(_A) > 0)
David Bommes's avatar
David Bommes committed
597
  {
598
599
600
601
    DEB_out(6, ", full ");
    if (cholmod_step_done_)
      direct_solver_.update_system_gmm(_A);
    else
David Bommes's avatar
David Bommes committed
602
    {
603
604
      direct_solver_.calc_system_gmm(_A);
      cholmod_step_done_ = true;
605
    }
606
607
608
609
    // const_cast<> to work around broken const correctness in Eigen
    direct_solver_.solve(_x, const_cast<Vecd&>(_rhs)); 

    ++n_full_;
David Bommes's avatar
David Bommes committed
610
611
  }

612
  DEB_line(6, "");
David Bommes's avatar
David Bommes committed
613
614
615
}

//-----------------------------------------------------------------------------
616

617
618
void MISolver::solve_multiple_rounding(
    CSCMatrix& _A, Vecd& _x, Vecd& _rhs, const Veci& _to_round)
David Bommes's avatar
David Bommes committed
619
{
620
  DEB_time_func_def;
David Bommes's avatar
David Bommes committed
621
622
  // some statistics
  n_local_ = 0;
623
624
  n_cg_ = 0;
  n_full_ = 0;
David Bommes's avatar
David Bommes committed
625
626
627
628
629

  // reset cholmod step flag
  cholmod_step_done_ = false;

  Veci to_round(_to_round);
630
631
632
633
634
  { // copy to round vector and make it unique
    std::sort(to_round.begin(), to_round.end());
    const auto last_unique = std::unique(to_round.begin(), to_round.end());
    to_round.resize(last_unique - to_round.begin());
  }
David Bommes's avatar
David Bommes committed
635

636
  if (initial_full_solution_)
David Bommes's avatar
David Bommes committed
637
  {
638
639
640
641
642
643
    DEB_out_if(noisy_ > 2, 2, "initial full solution\n");
    // TODO: we can throw more specific outcomes in the body of the
    // functions below
    COMISO_THROW_if(
        !direct_solver_.calc_system_gmm(_A), UNSPECIFIED_EIGEN_FAILURE);
    COMISO_THROW_if(!direct_solver_.solve(_x, _rhs), UNSPECIFIED_EIGEN_FAILURE);
David Bommes's avatar
David Bommes committed
644
645
646
647
648
649

    cholmod_step_done_ = true;

    ++n_full_;
  }

650
651
652
653
  //  data context for the main rounding cycle
  Vecui neigh_i;            // neighbors for local optimization
  std::vector<int> tr_best; // best indices to round
  RoundingQueue rndg_queue(multiple_rounding_threshold_); // rounding queue
David Bommes's avatar
David Bommes committed
654

655
656
657
658
#ifdef DEB_ON
  Base::StopWatch sw; // additional timer for the search next integer time
  double time_search_next_integer = 0;
#endif // DEB_ON
David Bommes's avatar
David Bommes committed
659
660

  // loop until solution computed
661
  while (!to_round.empty())
David Bommes's avatar
David Bommes committed
662
  {
663
664
    DEB_line(11, "Integer DOF's left: " << to_round.size());
    DEB_line(11, "residuum_norm: " << COMISO_GMM::residuum_norm(_A, _x, _rhs));
David Bommes's avatar
David Bommes committed
665

666
667
    DEB_only(sw.start());
    rndg_queue.clear();
668

David Bommes's avatar
David Bommes committed
669
    // find index yielding smallest rounding error
670
    for (unsigned int j = 0; j < to_round.size(); ++j)
David Bommes's avatar
David Bommes committed
671
    {
672
673
674
      const auto xr_j = _x[to_round[j]];
      const auto rnd_error = fabs(ROUND(xr_j) - xr_j);
      rndg_queue.add(j, rnd_error);
675
    }
676
    rndg_queue.get_ids(tr_best);
677

678
    DEB_only(time_search_next_integer += sw.stop());
David Bommes's avatar
David Bommes committed
679
680

    // nothing more to do?
681
    if (tr_best.empty())
David Bommes's avatar
David Bommes committed
682
683
      break;

684
    DEB_line(11, "rounding " << tr_best.size() << " variables simultaneously");
David Bommes's avatar
David Bommes committed
685
686
687
688

    // clear neigh for local update
    neigh_i.clear();

689
    for (const auto tr_indx : tr_best)
David Bommes's avatar
David Bommes committed
690
    {
691
692
      const unsigned i_cur = static_cast<unsigned>(to_round[tr_indx]);
      const double rnd_x = ROUND(_x[i_cur]); // store rounded value
David Bommes's avatar
David Bommes committed
693
694

      // compute neighbors
695
696
697
698
699
700
701
702
703
704
      const Col col = gmm::mat_const_col(_A, i_cur);
      for (auto it = gmm::vect_const_begin(col), ite = gmm::vect_const_end(col);
           it != ite; ++it)
      {
        if (it.index() != (unsigned int)i_cur)
          neigh_i.push_back(static_cast<int>(it.index()));
      }
      COMISO_GMM::fix_var_csc_symmetric(
          i_cur, rnd_x, _A, _x, _rhs); // eliminate
      to_round[tr_indx] = -1;
David Bommes's avatar
David Bommes committed
705
706
707
708
    }

    // 3-stage update of solution w.r.t. roundings
    // local GS / CG / SparseCholesky
709
    update_solution(_A, _x, _rhs, neigh_i);
710

711
712
    // remove all rounded indices from the to_round[] array
    for (const auto tr_indx : tr_best)
713
    {
714
715
716
717
718
719
      while (!to_round.empty() && to_round.back() < 0)
        to_round.pop_back(); // remove entries that are already rounded
      if (tr_indx >= to_round.size())
        continue; // already removed
      std::swap(to_round[tr_indx], to_round.back());
      to_round.pop_back();
720
721
722
    }
  }

723
724
  // final full solution?
  if (final_full_solution_ && gmm::mat_ncols(_A) > 0)
725
  {
726
727
728
729
730
731
732
733
    DEB_line(3, "final full solution");
    if (cholmod_step_done_)
      direct_solver_.update_system_gmm(_A);
    else
      direct_solver_.calc_system_gmm(_A);

    direct_solver_.solve(_x, _rhs);
    ++n_full_;
734
735
  }

736
737
738
739
740
741
742
743
  DEB_out_if(stats_, 2, // output statistics
      " *** Statistics of MiSo Solver ***"
          << "\n Number of CG    iterations  = " << n_cg_
          << "\n Number of LOCAL iterations  = " << n_local_
          << "\n Number of FULL  iterations  = " << n_full_
          << "\n Number of ROUNDING          = " << _to_round.size()
          << "\n time searching next integer = "
          << time_search_next_integer / 1000.0 << "s\n\n");
744
745
}

David Bommes's avatar
David Bommes committed
746
747
//-----------------------------------------------------------------------------

748
749
void MISolver::solve_gurobi(
    CSCMatrix& _A, Vecd& _x, Vecd& _rhs, Veci& _to_round)
David Bommes's avatar
David Bommes committed
750
{
751
  DEB_enter_func;
David Bommes's avatar
David Bommes committed
752
753
754
755
#if COMISO_GUROBI_AVAILABLE

  // get round-indices in set
  std::set<int> to_round;
756
  for (unsigned int i = 0; i < _to_round.size(); ++i)
David Bommes's avatar
David Bommes committed
757
758
    to_round.insert(_to_round[i]);

759
760
  try
  {
David Bommes's avatar
David Bommes committed
761
762
763
764
765
766
767
768
769
770
771
    GRBEnv env = GRBEnv();

    GRBModel model = GRBModel(env);

    // set time limite
    model.getEnv().set(GRB_DoubleParam_TimeLimit, gurobi_max_time_);

    unsigned int n = _rhs.size();

    // 1. allocate variables
    std::vector<GRBVar> vars;
772
773
774
775
    for (unsigned int i = 0; i < n; ++i)
      if (to_round.count(i))
        vars.push_back(
            model.addVar(-GRB_INFINITY, GRB_INFINITY, 0.0, GRB_INTEGER));
David Bommes's avatar
David Bommes committed
776
      else
777
778
        vars.push_back(
            model.addVar(-GRB_INFINITY, GRB_INFINITY, 0.0, GRB_CONTINUOUS));
David Bommes's avatar
David Bommes committed
779
780
781
782
783
784
785
786
787

    // Integrate new variables
    model.update();

    // 2. setup_energy

    // build objective function from linear system E = x^tAx - 2x^t*rhs
    GRBQuadExpr objective;

788
789
    for (unsigned int i = 0; i < _A.nc; ++i)
      for (unsigned int j = _A.jc[i]; j < _A.jc[i + 1]; ++j)
David Bommes's avatar
David Bommes committed
790
      {
791
        objective += _A.pr[j] * vars[_A.ir[j]] * vars[i];
David Bommes's avatar
David Bommes committed
792
      }
793
794
    for (unsigned int i = 0; i < n; ++i)
      objective -= 2 * _rhs[i] * vars[i];
David Bommes's avatar
David Bommes committed
795

796
797
798
799
800
    //    _A.jc[c+1]
    //    _A.pr[write]
    //    _A.ir[write]
    //    _A.nc
    //    _A.nr
David Bommes's avatar
David Bommes committed
801
802
803
804
805
806
807
808
809
810

    // minimize
    model.set(GRB_IntAttr_ModelSense, 1);
    model.setObjective(objective);

    // 4. solve
    model.optimize();

    // 5. store result
    _x.resize(n);
811
    for (unsigned int i = 0; i < n; ++i)
David Bommes's avatar
David Bommes committed
812
813
      _x[i] = vars[i].get(GRB_DoubleAttr_X);

814
815
    DEB_out(
        2, "GUROBI objective: " << model.get(GRB_DoubleAttr_ObjVal) << "\n");
David Bommes's avatar
David Bommes committed
816
  }
817
  catch (GRBException& e)
David Bommes's avatar
David Bommes committed
818
  {
819
    PROGRESS_RESUME_ABORT; // resume a processed abort request
820
821
    DEB_warning(2,
        "Error code = " << e.getErrorCode() << "[" << e.getMessage() << "]\n");
David Bommes's avatar
David Bommes committed
822
  }
823
  catch (...)
David Bommes's avatar
David Bommes committed
824
  {
825
    PROGRESS_RESUME_ABORT; // resume a processed abort request
826
    DEB_warning(1, "Exception during optimization");
David Bommes's avatar
David Bommes committed
827
828
829
  }

#else
830
  DEB_out(1, "GUROBI solver is not available, please install it...\n");
David Bommes's avatar
David Bommes committed
831
832
833
#endif
}

834
835
//----------------------------------------------------------------------------

836
void MISolver::show_options_dialog()
837
{
838
  DEB_enter_func;
839
#if (COMISO_QT4_AVAILABLE)
840
841
  MISolverDialog* pd = new MISolverDialog(*this);
  pd->exec();
David Bommes's avatar
changed    
David Bommes committed
842
#else
843
  DEB_warning(1, "Qt not available to show solver dialog!!!");
844
845
846
#endif
}

847
// end namespace COMISO
848
} // namespace COMISO