1 // Copyright (c) 2018 ERGO-Code. See license.txt for license.
2
3 #include "basis.h"
4 #include <algorithm>
5 #include <cmath>
6 #include <tuple>
7 #include "basiclu_kernel.h"
8 #include "basiclu_wrapper.h"
9 #include "forrest_tomlin.h"
10 #include "guess_basis.h"
11 #include "power_method.h"
12 #include "symbolic_invert.h"
13 #include "timer.h"
14 #include "utils.h"
15
16 namespace ipx {
17
Basis(const Control & control,const Model & model)18 Basis::Basis(const Control& control, const Model& model)
19 : control_(control), model_(model) {
20 const Int m = model_.rows();
21 const Int n = model_.cols();
22 basis_.resize(m);
23 map2basis_.resize(n+m);
24 if (control_.lu_kernel() <= 0) {
25 lu_.reset(new BasicLu(control_, m));
26 } else {
27 std::unique_ptr<LuFactorization> lu(new BasicLuKernel);
28 lu_.reset(new ForrestTomlin(control_, m, lu));
29 }
30 lu_->pivottol(control_.lu_pivottol());
31 SetToSlackBasis();
32 }
33
FixNonbasicVariable(Int j)34 void Basis::FixNonbasicVariable(Int j) {
35 if (StatusOf(j) == NONBASIC_FIXED)
36 return;
37 assert(StatusOf(j) == NONBASIC);
38 assert(map2basis_[j] == -1);
39 map2basis_[j] = -2;
40 }
41
FreeBasicVariable(Int j)42 void Basis::FreeBasicVariable(Int j) {
43 const Int m = model_.rows();
44 if (StatusOf(j) == BASIC_FREE)
45 return;
46 assert(StatusOf(j) == BASIC);
47 assert(map2basis_[j] >= 0 && map2basis_[j] < m);
48 map2basis_[j] += m;
49 }
50
UnfixVariables()51 void Basis::UnfixVariables() {
52 const Int m = model_.rows();
53 const Int n = model_.cols();
54 for (Int j = 0; j < n+m; j++)
55 if (map2basis_[j] == -2)
56 map2basis_[j] = -1;
57 }
58
UnfreeVariables()59 void Basis::UnfreeVariables() {
60 const Int m = model_.rows();
61 const Int n = model_.cols();
62 for (Int j = 0; j < n+m; j++)
63 if (map2basis_[j] >= m)
64 map2basis_[j] -= m;
65 }
66
SetToSlackBasis()67 void Basis::SetToSlackBasis() {
68 const Int m = model_.rows();
69 const Int n = model_.cols();
70 for (Int i = 0; i < m; i++)
71 basis_[i] = n+i;
72 for (Int j = 0; j < n; j++)
73 map2basis_[j] = -1;
74 for (Int i = 0; i < m; i++)
75 map2basis_[n+i] = i;
76 Int err = Factorize();
77 // factorization of slack basis cannot fail other than out of memory
78 assert(err == 0);
79 }
80
Load(const int * basic_status)81 Int Basis::Load(const int* basic_status) {
82 const Int m = model_.rows();
83 const Int n = model_.cols();
84
85 // Change member variables only when basis is valid.
86 std::vector<Int> basis, map2basis(n+m);
87 Int p = 0;
88 for (Int j = 0; j < n+m; j++) {
89 switch (basic_status[j]) {
90 case NONBASIC_FIXED:
91 map2basis[j] = -2;
92 break;
93 case NONBASIC:
94 map2basis[j] = -1;
95 break;
96 case BASIC:
97 basis.push_back(j);
98 map2basis[j] = p++;
99 break;
100 case BASIC_FREE:
101 basis.push_back(j);
102 map2basis[j] = p++ + m;
103 break;
104 default:
105 return IPX_ERROR_invalid_basis;
106 }
107 }
108 if (p != m)
109 return IPX_ERROR_invalid_basis;
110
111 std::copy(basis.begin(), basis.end(), basis_.begin());
112 std::copy(map2basis.begin(), map2basis.end(), map2basis_.begin());
113 return Factorize();
114 }
115
Factorize()116 Int Basis::Factorize() {
117 const Int m = model_.rows();
118 const SparseMatrix& AI = model_.AI();
119 Timer timer;
120
121 // Build column pointers for passing to LU factorization.
122 std::vector<Int> begin(m), end(m);
123 for (Int i = 0; i < m; i++) {
124 assert(basis_[i] >= 0);
125 begin[i] = AI.begin(basis_[i]);
126 end[i] = AI.end(basis_[i]);
127 }
128
129 Int err = 0; // return code
130 while (true) {
131 Int flag = lu_->Factorize(begin.data(), end.data(), AI.rowidx(),
132 AI.values(), false);
133 num_factorizations_++;
134 fill_factors_.push_back(lu_->fill_factor());
135 if (flag & 2) {
136 AdaptToSingularFactorization();
137 err = IPX_ERROR_basis_singular;
138 break;
139 }
140 if ((flag & 1) && TightenLuPivotTol()) {
141 // The factorization was numerically unstable and the pivot
142 // tolerance could be tightened. Repeat.
143 continue;
144 }
145 if (flag & 1) {
146 control_.Debug(3)
147 << " LU factorization unstable with pivot tolerance "
148 << lu_->pivottol() << '\n';
149 // ignore instability
150 }
151 break;
152 }
153 time_factorize_ += timer.Elapsed();
154 factorization_is_fresh_ = true;
155 return err;
156 }
157
FactorizationIsFresh() const158 bool Basis::FactorizationIsFresh() const {
159 return factorization_is_fresh_;
160 }
161
GetLuFactors(SparseMatrix * L,SparseMatrix * U,Int * rowperm,Int * colperm) const162 void Basis::GetLuFactors(SparseMatrix *L, SparseMatrix *U, Int *rowperm,
163 Int *colperm) const {
164 assert(FactorizationIsFresh());
165 lu_->GetFactors(L, U, rowperm, colperm, nullptr);
166 }
167
SolveDense(const Vector & rhs,Vector & lhs,char trans) const168 void Basis::SolveDense(const Vector& rhs, Vector& lhs, char trans) const {
169 lu_->SolveDense(rhs, lhs, trans);
170 }
171
SolveForUpdate(Int j,IndexedVector & lhs)172 void Basis::SolveForUpdate(Int j, IndexedVector& lhs) {
173 const Int p = PositionOf(j);
174 Timer timer;
175 if (p < 0) { // ftran
176 const SparseMatrix& AI = model_.AI();
177 Int begin = AI.begin(j);
178 Int end = AI.end(j);
179 Int nz = end-begin;
180 const Int* bi = AI.rowidx() + begin;
181 const double* bx = AI.values() + begin;
182 lu_->FtranForUpdate(nz, bi, bx, lhs);
183 num_ftran_++;
184 if (lhs.sparse())
185 num_ftran_sparse_++;
186 time_ftran_ += timer.Elapsed();
187 }
188 else { // btran
189 lu_->BtranForUpdate(p, lhs);
190 num_btran_++;
191 if (lhs.sparse())
192 num_btran_sparse_++;
193 time_btran_ += timer.Elapsed();
194 }
195 }
196
SolveForUpdate(Int j)197 void Basis::SolveForUpdate(Int j) {
198 const Int p = PositionOf(j);
199 Timer timer;
200 if (p < 0) {
201 const SparseMatrix& AI = model_.AI();
202 Int begin = AI.begin(j);
203 Int end = AI.end(j);
204 Int nz = end-begin;
205 const Int* bi = AI.rowidx() + begin;
206 const double* bx = AI.values() + begin;
207 lu_->FtranForUpdate(nz, bi, bx);
208 time_ftran_ += timer.Elapsed();
209 }
210 else {
211 lu_->BtranForUpdate(p);
212 time_btran_ += timer.Elapsed();
213 }
214 }
215
TableauRow(Int jb,IndexedVector & btran,IndexedVector & row,bool ignore_fixed)216 void Basis::TableauRow(Int jb, IndexedVector& btran, IndexedVector& row,
217 bool ignore_fixed) {
218 const Int m = model_.rows();
219 const Int n = model_.cols();
220 assert(IsBasic(jb));
221 SolveForUpdate(jb, btran);
222
223 // Estimate if tableau row is sparse.
224 bool is_sparse = btran.sparse();
225 if (is_sparse) {
226 const SparseMatrix& AIt = model_.AIt();
227 const Int* bi = btran.pattern();
228 Int nz = 0;
229 for (Int k = 0; k < btran.nnz(); k++) {
230 Int i = bi[k];
231 nz += AIt.end(i) - AIt.begin(i);
232 }
233 nz /= 2; // guess for overlap
234 if (nz > kHypersparseThreshold*n)
235 is_sparse = false;
236 }
237
238 if (is_sparse) {
239 // sparse vector * sparse matrix: accesses A rowwise
240 const SparseMatrix& AIt = model_.AIt();
241 const Int* Ati = AIt.rowidx();
242 const double* Atx = AIt.values();
243 const Int* bi = btran.pattern();
244 row.set_to_zero();
245 Int* row_pattern = row.pattern();
246 Int nz = 0;
247 for (Int k = 0; k < btran.nnz(); k++) {
248 Int i = bi[k];
249 double temp = btran[i];
250 Int begin = AIt.begin(i);
251 Int end = AIt.end(i);
252 for (Int p = begin; p < end; p++) {
253 Int j = Ati[p];
254 if (map2basis_[j] == -1 || map2basis_[j] == -2 && !ignore_fixed)
255 {
256 map2basis_[j] -= 2; // mark column
257 row_pattern[nz++] = j;
258 }
259 if (map2basis_[j] < -2) // marked column
260 row[j] += temp * Atx[p];
261 }
262 }
263 for (Int k = 0; k < nz; k++) // reset marked
264 map2basis_[row_pattern[k]] += 2;
265 row.set_nnz(nz);
266 } else {
267 // dense vector * sparse matrix: accesses A columnwise
268 const SparseMatrix& AI = model_.AI();
269 const Int* Ai = AI.rowidx();
270 const double* Ax = AI.values();
271 for (Int j = 0; j < n+m; j++) {
272 double result = 0.0;
273 if (map2basis_[j] == -1 || map2basis_[j] == -2 && !ignore_fixed) {
274 Int begin = AI.begin(j);
275 Int end = AI.end(j);
276 for (Int p = begin; p < end; p++)
277 result += Ax[p] * btran[Ai[p]];
278 }
279 row[j] = result;
280 }
281 row.InvalidatePattern();
282 }
283 }
284
ExchangeIfStable(Int jb,Int jn,double tableau_entry,int sys,bool * exchanged)285 Int Basis::ExchangeIfStable(Int jb, Int jn, double tableau_entry, int sys,
286 bool* exchanged) {
287 assert(IsBasic(jb));
288 assert(IsNonbasic(jn));
289 if (sys > 0) // forward system needs to be solved
290 SolveForUpdate(jn);
291 if (sys < 0) // transposed system needs to be solved
292 SolveForUpdate(jb);
293
294 // Update factorization.
295 *exchanged = false;
296 Timer timer;
297 Int err = lu_->Update(tableau_entry);
298 time_update_ += timer.Elapsed();
299 if (err != 0) {
300 if (FactorizationIsFresh() && !TightenLuPivotTol())
301 return IPX_ERROR_basis_too_ill_conditioned;
302 control_.Debug(3)
303 << " stability check forced refactorization after "
304 << lu_->updates()-1 << " updates\n";
305 return Factorize(); // refactorizes the old basis
306 }
307
308 // Update basis.
309 Int ib = PositionOf(jb);
310 assert(basis_[ib] == jb);
311 basis_[ib] = jn;
312 map2basis_[jn] = ib; // status now BASIC
313 map2basis_[jb] = -1; // status now NONBASIC
314 num_updates_++;
315 factorization_is_fresh_ = false;
316 *exchanged = true;
317
318 if (lu_->NeedFreshFactorization())
319 return Factorize();
320 return 0;
321 }
322
ComputeBasicSolution(Vector & x,Vector & y,Vector & z) const323 void Basis::ComputeBasicSolution(Vector& x, Vector& y, Vector& z) const {
324 const Int m = model_.rows();
325 const Int n = model_.cols();
326 const Vector& b = model_.b();
327 const Vector& c = model_.c();
328 const SparseMatrix& AI = model_.AI();
329
330 // Compute x[basic] so that Ax=b. Use y as workspace.
331 y = b;
332 for (Int j = 0; j < n+m; j++)
333 if (IsNonbasic(j))
334 ScatterColumn(AI, j, -x[j], y);
335 SolveDense(y, y, 'N');
336 for (Int p = 0; p < m; p++)
337 x[basis_[p]] = y[p];
338
339 // Compute y and z[nonbasic] so that AI'y+z=c.
340 for (Int p = 0; p < m; p++)
341 y[p] = c[basis_[p]] - z[basis_[p]];
342 SolveDense(y, y, 'T');
343 for (Int j = 0; j < n+m; j++) {
344 if (IsNonbasic(j))
345 z[j] = c[j] - DotColumn(AI, j, y);
346 }
347 }
348
ConstructBasisFromWeights(const double * colscale,Info * info)349 void Basis::ConstructBasisFromWeights(const double* colscale, Info* info) {
350 const Int m = model_.rows();
351 const Int n = model_.cols();
352 assert(colscale);
353 info->errflag = 0;
354 info->dependent_rows = 0;
355 info->dependent_cols = 0;
356
357 if (control_.crash_basis()) {
358 CrashBasis(colscale);
359 double sigma = MinSingularValue();
360 control_.Debug()
361 << Textline("Minimum singular value of crash basis:") << sci2(sigma)
362 << '\n';
363 Repair(info);
364 if (info->basis_repairs < 0) {
365 control_.Log() << " discarding crash basis\n";
366 SetToSlackBasis();
367 }
368 else if (info->basis_repairs > 0) {
369 sigma = MinSingularValue();
370 control_.Debug()
371 << Textline("Minimum singular value of repaired crash basis:")
372 << sci2(sigma) << '\n';
373 }
374 } else {
375 SetToSlackBasis();
376 }
377 PivotFreeVariablesIntoBasis(colscale, info);
378 if (info->errflag)
379 return;
380 PivotFixedVariablesOutOfBasis(colscale, info);
381 if (info->errflag)
382 return;
383 }
384
MinSingularValue() const385 double Basis::MinSingularValue() const {
386 const Int m = model_.rows();
387 Vector v(m);
388
389 // Computes maximum eigenvalue of inverse(B*B').
390 double lambda = PowerMethod(
391 [this](const Vector& x, Vector& fx) {
392 SolveDense(x, fx, 'N');
393 SolveDense(fx, fx, 'T'); }, v);
394 return std::sqrt(1.0/lambda);
395 }
396
SymbolicInvert(Int * rowcounts,Int * colcounts) const397 void Basis::SymbolicInvert(Int* rowcounts, Int* colcounts) const {
398 ipx::SymbolicInvert(model_, basis_, rowcounts, colcounts);
399 }
400
DensityInverse() const401 double Basis::DensityInverse() const {
402 Int m = model_.rows();
403 std::vector<Int> rowcounts(m);
404 SymbolicInvert(rowcounts.data(), nullptr);
405 // Accumulating rowcounts would result in overflow for large LPs.
406 double density = 0.0;
407 for (Int i = 0; i < m; i++)
408 density += 1.0*rowcounts[i]/m;
409 return density/m;
410 }
411
model() const412 const Model& Basis::model() const {
413 return model_;
414 }
415
factorizations() const416 Int Basis::factorizations() const {
417 return num_factorizations_;
418 }
419
updates_total() const420 Int Basis::updates_total() const {
421 return num_updates_;
422 }
423
frac_ftran_sparse() const424 double Basis::frac_ftran_sparse() const {
425 return 1.0 * num_ftran_sparse_ / num_ftran_;
426 }
427
frac_btran_sparse() const428 double Basis::frac_btran_sparse() const {
429 return 1.0 * num_btran_sparse_ / num_btran_;
430 }
431
time_factorize() const432 double Basis::time_factorize() const {
433 return time_factorize_;
434 }
435
time_ftran() const436 double Basis::time_ftran() const {
437 return time_ftran_;
438 }
439
time_btran() const440 double Basis::time_btran() const {
441 return time_btran_;
442 }
443
time_update() const444 double Basis::time_update() const {
445 return time_update_;
446 }
447
mean_fill() const448 double Basis::mean_fill() const {
449 if (fill_factors_.empty())
450 return 0.0;
451 double mean = 1.0;
452 Int num_factors = fill_factors_.size();
453 for (double f : fill_factors_)
454 mean *= std::pow(f, 1.0/num_factors);
455 return mean;
456 }
457
max_fill() const458 double Basis::max_fill() const {
459 if (fill_factors_.empty())
460 return 0.0;
461 return *std::max_element(fill_factors_.begin(), fill_factors_.end());
462 }
463
AdaptToSingularFactorization()464 Int Basis::AdaptToSingularFactorization() {
465 const Int m = model_.rows();
466 const Int n = model_.cols();
467 std::vector<Int> rowperm(m), colperm(m), dependent_cols;
468
469 lu_->GetFactors(nullptr, nullptr, rowperm.data(), colperm.data(),
470 &dependent_cols);
471 for (Int k : dependent_cols) {
472 // Column p of the basis matrix was replaced by the i-th unit
473 // column. Insert the corresponding slack variable jn into
474 // position p of the basis.
475 Int p = colperm[k];
476 Int i = rowperm[k];
477 Int jb = basis_[p];
478 Int jn = n+i;
479 assert(map2basis_[jn] < 0);
480 basis_[p] = jn;
481 map2basis_[jn] = p; // now BASIC at position p
482 if (jb >= 0)
483 map2basis_[jb] = -1; // now NONBASIC
484 }
485 return dependent_cols.size();
486 }
487
TightenLuPivotTol()488 bool Basis::TightenLuPivotTol() {
489 double tol = lu_->pivottol();
490 if (tol <= 0.05)
491 lu_->pivottol(0.1);
492 else if (tol <= 0.25)
493 lu_->pivottol(0.3);
494 else if (tol <= 0.5)
495 lu_->pivottol(0.9);
496 else
497 return false;
498 control_.Log()
499 << " LU pivot tolerance tightened to " << lu_->pivottol() << '\n';
500 return true;
501 }
502
CrashBasis(const double * colweights)503 void Basis::CrashBasis(const double* colweights) {
504 const Int m = model_.rows();
505
506 // Make a guess for a basis. Then use LU factorization with a strict
507 // absolute pivot tolerance to remove dependent columns. This is not a
508 // rank revealing factorization, but it detects many dependencies in
509 // practice.
510 std::vector<Int> cols_guessed = GuessBasis(control_, model_, colweights);
511 assert(cols_guessed.size() <= m);
512 assert(cols_guessed.size() == m); // at the moment
513
514 // Initialize the Basis object and factorize the (partial) basis. If
515 // basis_[p] is negative, the p-th column of the basis matrix is zero,
516 // and a slack column will be inserted by CrashFacorize().
517 std::fill(basis_.begin(), basis_.end(), -1);
518 std::fill(map2basis_.begin(), map2basis_.end(), -1);
519 for (Int k = 0; k < cols_guessed.size(); k++) {
520 basis_[k] = cols_guessed[k];
521 assert(map2basis_[basis_[k]] == -1); // must not have duplicates
522 map2basis_[basis_[k]] = k;
523 }
524 Int num_dropped = 0;
525 CrashFactorize(&num_dropped);
526 control_.Debug()
527 << Textline("Number of columns dropped from guessed basis:")
528 << num_dropped << '\n';
529 }
530
531 // Rook search for a large entry in inverse(B). If successful, returns [p,i,x]
532 // where x is the entry at position (p,i) of inverse(B). Notice that p
533 // corresponds to a column of B and i corresponds to a row of B. If overflow
534 // occurs, returns [-1,-1,INFINITY]. See [1] for a discussion of the algorithm.
535 //
536 // [1] N.J. Higham and S.D. Relton, "Estimating the Largest Elements of a
537 // Matrix", MIMS EPrint 2015.116, University of Manchester (2015).
538 //
InverseSearch(const Basis & basis,Vector & work)539 static std::tuple<Int,Int,double> InverseSearch(const Basis& basis,
540 Vector& work) {
541 const Int m = work.size();
542 double inverse_max = 0.0;
543
544 for (Int i = 0; i < m; i++)
545 work[i] = 1.0/(i+1);
546
547 while (true) {
548 basis.SolveDense(work, work, 'N');
549 if (!AllFinite(work))
550 break;
551 Int pmax = FindMaxAbs(work);
552 work = 0.0;
553 work[pmax] = 1.0;
554 basis.SolveDense(work, work, 'T');
555 if (!AllFinite(work))
556 break;
557 Int imax = FindMaxAbs(work);
558 double inverse_entry = work[imax];
559 if (std::abs(inverse_entry) <= 2.0*inverse_max)
560 return std::make_tuple(pmax, imax, inverse_entry);
561 inverse_max = std::abs(inverse_entry);
562 work = 0.0;
563 work[imax] = 1.0;
564 }
565 return std::make_tuple(-1,-1,INFINITY); // failure
566 }
567
Repair(Info * info)568 void Basis::Repair(Info* info) {
569 const Int m = model_.rows();
570 const Int n = model_.cols();
571 Vector work(m);
572 info->basis_repairs = 0;
573
574 while (true) {
575 std::tuple<Int,Int,double> entry = InverseSearch(*this, work);
576 Int pmax = std::get<0>(entry);
577 Int imax = std::get<1>(entry);
578 double pivot = std::get<2>(entry);
579 if (pmax < 0 || imax < 0 || !std::isfinite(pivot)) {
580 info->basis_repairs = -1;
581 break;
582 }
583 if (std::abs(pivot) < kBasisRepairThreshold)
584 break;
585 Int jb = basis_[pmax];
586 Int jn = n + imax;
587 if (!IsNonbasic(jn)) {
588 info->basis_repairs = -2;
589 break;
590 }
591 if (info->basis_repairs >= kMaxBasisRepair) {
592 info->basis_repairs = -3;
593 break;
594 }
595 SolveForUpdate(jb);
596 SolveForUpdate(jn);
597 CrashExchange(jb, jn, pivot, 0, nullptr);
598 info->basis_repairs++;
599 control_.Debug(3) << " basis repair: |pivot| = "
600 << sci2(std::abs(pivot)) << '\n';
601 }
602 }
603
CrashFactorize(Int * num_dropped)604 void Basis::CrashFactorize(Int* num_dropped) {
605 const Int m = model_.rows();
606 const SparseMatrix& AI = model_.AI();
607 Timer timer;
608
609 // Build column pointers for passing to LU factorization. A negative index
610 // in basis_ means an empty slot. This option is kept for use by the crash
611 // procedure, if an incomplete basis was constructed. The zero column in the
612 // basis matrix causes a singularity in the LU factorization, so that the
613 // empty slot will be replaced by a slack variable below.
614 std::vector<Int> begin(m), end(m);
615 for (Int i = 0; i < m; i++) {
616 if (basis_[i] >= 0) {
617 begin[i] = AI.begin(basis_[i]);
618 end[i] = AI.end(basis_[i]);
619 } else {
620 begin[i] = 0;
621 end[i] = 0;
622 }
623 }
624 Int flag = lu_->Factorize(begin.data(), end.data(), AI.rowidx(),
625 AI.values(), true);
626 num_factorizations_++;
627 fill_factors_.push_back(lu_->fill_factor());
628 Int ndropped = 0;
629 if (flag & 2)
630 ndropped = AdaptToSingularFactorization();
631 if (num_dropped)
632 *num_dropped = ndropped;
633
634 time_factorize_ += timer.Elapsed();
635 factorization_is_fresh_ = true;
636
637 #ifndef NDEBUG
638 // All empty slots must have been replaced by slack variables.
639 for (Int i = 0; i < m; i++)
640 assert(basis_[i] >= 0);
641 #endif
642 }
643
CrashExchange(Int jb,Int jn,double tableau_entry,int sys,Int * num_dropped)644 void Basis::CrashExchange(Int jb, Int jn, double tableau_entry, int sys,
645 Int* num_dropped) {
646 assert(IsBasic(jb));
647 assert(IsNonbasic(jn));
648 if (sys > 0) // forward system needs to be solved
649 SolveForUpdate(jn);
650 if (sys < 0) // transposed system needs to be solved
651 SolveForUpdate(jb);
652
653 // Update basis.
654 Int ib = PositionOf(jb);
655 assert(basis_[ib] == jb);
656 basis_[ib] = jn;
657 map2basis_[jn] = ib; // status now BASIC
658 map2basis_[jb] = -1; // status now NONBASIC
659 num_updates_++;
660 factorization_is_fresh_ = false;
661
662 // Update factorization.
663 if (num_dropped)
664 *num_dropped = 0;
665 Timer timer;
666 Int err = lu_->Update(tableau_entry);
667 time_update_ += timer.Elapsed();
668 if (err != 0 || lu_->NeedFreshFactorization()) {
669 control_.Debug(3) << " refactorization required in CrashExchange()\n";
670 CrashFactorize(num_dropped);
671 }
672 }
673
PivotFreeVariablesIntoBasis(const double * colweights,Info * info)674 void Basis::PivotFreeVariablesIntoBasis(const double* colweights, Info* info) {
675 const Int m = model_.rows();
676 const Int n = model_.cols();
677 IndexedVector ftran(m);
678 const double dependency_tol = std::max(0.0, control_.dependency_tol());
679 info->errflag = 0;
680 info->dependent_cols = 0;
681 Int stability_pivots = 0;
682
683 // Maintain stack of free nonbasic variables.
684 std::vector<Int> remaining;
685 for (Int j = 0; j < n+m; j++) {
686 if (std::isinf(colweights[j]) && map2basis_[j] < 0)
687 remaining.push_back(j);
688 }
689 control_.Debug() << Textline("Number of free variables nonbasic:")
690 << remaining.size() << '\n';
691
692 control_.ResetPrintInterval();
693 while (!remaining.empty()) {
694 Int jn = remaining.back();
695 assert(std::isinf(colweights[jn]));
696 assert(map2basis_[jn] < 0);
697 if ((info->errflag = control_.InterruptCheck()) != 0)
698 return;
699
700 SolveForUpdate(jn, ftran);
701 Int pmax = -1;
702 Int pmax_nonfree = -1;
703 double fmax = 0.0;
704 double fmax_nonfree = 0.0;
705 auto update_max = [&](Int p, double f) {
706 f = std::abs(f);
707 if (f > fmax) {
708 fmax = f;
709 pmax = p;
710 }
711 if (!std::isinf(colweights[basis_[p]])) {
712 if (f > fmax_nonfree) {
713 fmax_nonfree = f;
714 pmax_nonfree = p;
715 }
716 }
717 };
718 for_each_nonzero(ftran, update_max);
719
720 if (fmax > 4.0 && fmax_nonfree < 1.0) {
721 Int jb = basis_[pmax];
722 assert(std::isinf(colweights[jb]));
723 bool exchanged;
724 info->errflag = ExchangeIfStable(jb, jn, ftran[pmax], -1,
725 &exchanged);
726 if (info->errflag)
727 return;
728 if (!exchanged) // factorization was unstable, try again
729 continue;
730 remaining.pop_back();
731 remaining.push_back(jb);
732 info->updates_start++;
733 stability_pivots++;
734 } else {
735 if (fmax_nonfree <= dependency_tol) {
736 // jn cannot be pivoted into the basis. If we do not have an
737 // unbounded primal ray yet, then test if column jn yields one.
738 // Compute the change in the primal objective that is caused by
739 // a unit increase of x[jn] with corresponding adjustment of
740 // free basic variables.
741 if (!info->cols_inconsistent) {
742 const Vector& c = model_.c();
743 double delta_obj = c[jn];
744 auto update_delta_obj = [&](Int p, double f) {
745 Int j = basis_[p];
746 if (std::isinf(colweights[j]))
747 delta_obj -= c[j] * f;
748 };
749 for_each_nonzero(ftran, update_delta_obj);
750 if (std::abs(delta_obj) > dependency_tol) {
751 control_.Debug()
752 << Textline(
753 "Unbounded primal ray with objective change:")
754 << sci2(delta_obj) << '\n';
755 info->cols_inconsistent = true;
756 }
757 }
758 info->dependent_cols++;
759 remaining.pop_back();
760 } else {
761 Int jb = basis_[pmax_nonfree];
762 bool exchanged;
763 info->errflag = ExchangeIfStable(jb, jn, ftran[pmax_nonfree],
764 -1, &exchanged);
765 if (info->errflag)
766 return;
767 if (!exchanged) // factorization was unstable, try again
768 continue;
769 remaining.pop_back();
770 info->updates_start++;
771 }
772 }
773 control_.IntervalLog()
774 << " " << remaining.size() << " free variables remaining\n";
775 }
776 control_.Debug()
777 << Textline("Number of free variables swapped for stability:")
778 << stability_pivots << '\n';
779 }
780
PivotFixedVariablesOutOfBasis(const double * colweights,Info * info)781 void Basis::PivotFixedVariablesOutOfBasis(const double* colweights, Info* info){
782 const Int m = model_.rows();
783 const Int n = model_.cols();
784 IndexedVector btran(m), row(n+m);
785 const double dependency_tol = std::max(0.0, control_.dependency_tol());
786 info->errflag = 0;
787 info->dependent_rows = 0;
788 Int stability_pivots = 0;
789
790 // Maintain stack of fixed basic variables.
791 std::vector<Int> remaining;
792 for (Int j = 0; j < n; j++) {
793 // Structural variables with zero weight must not be in the crash basis.
794 if (colweights[j] == 0.0)
795 assert(map2basis_[j] < 0);
796 }
797 for (Int j = n; j < n+m; j++) {
798 if (colweights[j] == 0.0 && map2basis_[j] >= 0)
799 remaining.push_back(j);
800 }
801 control_.Debug() << Textline("Number of fixed variables basic:")
802 << remaining.size() << '\n';
803
804 control_.ResetPrintInterval();
805 while (!remaining.empty()) {
806 Int jb = remaining.back();
807 assert(colweights[jb] == 0.0);
808 assert(map2basis_[jb] >= 0);
809 if ((info->errflag = control_.InterruptCheck()) != 0)
810 return;
811
812 TableauRow(jb, btran, row);
813 Int jmax = -1;
814 Int jmax_nonfixed = -1;
815 double rmax = 0.0;
816 double rmax_nonfixed = 0.0;
817 auto update_max = [&](Int j, double r) {
818 // Ignore structural variables with zero weight.
819 if (j >= n || colweights[j] != 0.0) {
820 r = std::abs(r);
821 if (r > rmax) {
822 rmax = r;
823 jmax = j;
824 }
825 if (colweights[j] != 0.0) {
826 if (r > rmax_nonfixed) {
827 rmax_nonfixed = r;
828 jmax_nonfixed = j;
829 }
830 }
831 }
832 };
833 for_each_nonzero(row, update_max);
834
835 if (rmax > 4.0 && rmax_nonfixed < 1.0) {
836 assert(colweights[jmax] == 0.0);
837 bool exchanged;
838 info->errflag = ExchangeIfStable(jb, jmax, row[jmax], 1,
839 &exchanged);
840 if (info->errflag)
841 return;
842 if (!exchanged) // factorization was unstable, try again
843 continue;
844 remaining.pop_back();
845 remaining.push_back(jmax);
846 info->updates_start++;
847 stability_pivots++;
848 } else {
849 if (rmax_nonfixed <= dependency_tol) {
850 // jb cannot be pivoted out of the basis. If we do not have an
851 // unbounded dual ray yet, then test if row jb-n yields one.
852 // Compute the change in the dual objective that is caused by a
853 // unit increase of y[jb-n] with corresponding adjustment of the
854 // remaining y[i].
855 if (!info->rows_inconsistent) {
856 double delta_obj = Dot(btran, model_.b());
857 if (std::abs(delta_obj) > dependency_tol) {
858 control_.Debug()
859 << Textline(
860 "Unbounded dual ray with objective change:")
861 << sci2(delta_obj) << '\n';
862 info->rows_inconsistent = true;
863 }
864 }
865 info->dependent_rows++;
866 remaining.pop_back();
867 } else {
868 // jb can be exchanged by a non-fixed variable.
869 // Among all numerically stable pivots, choose the one that
870 // maximizes volume of the basis matrix.
871 Int jmax_scaled = -1;
872 double rmax_scaled = 0.0;
873 auto update_max = [&](Int j, double r) {
874 r = std::abs(r);
875 double rscaled = r * colweights[j];
876 if (r >= 0.1*rmax_nonfixed && rscaled > rmax_scaled) {
877 rmax_scaled = rscaled;
878 jmax_scaled = j;
879 }
880 };
881 for_each_nonzero(row, update_max);
882 assert(jmax_scaled >= 0);
883 bool exchanged;
884 double pivot = row[jmax_scaled];
885 info->errflag = ExchangeIfStable(jb, jmax_scaled, pivot, 1,
886 &exchanged);
887 if (info->errflag)
888 return;
889 if (!exchanged) // factorization was unstable, try again
890 continue;
891 remaining.pop_back();
892 info->updates_start++;
893 }
894 }
895 control_.IntervalLog()
896 << " " << remaining.size() << " fixed variables remaining\n";
897 }
898 control_.Debug()
899 << Textline("Number of fixed variables swapped for stability:")
900 << stability_pivots << '\n';
901 }
902
CopyBasic(const Vector & x,const Basis & basis)903 Vector CopyBasic(const Vector& x, const Basis& basis) {
904 const Int m = basis.model().rows();
905 Vector xbasic(m);
906 for (Int p = 0; p < m; p++)
907 xbasic[p] = x[basis[p]];
908 return xbasic;
909 }
910
911 } // namespace ipx
912