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