Skip to content

Commit df32477

Browse files
committed
Merge branch 'hi-pdlp-jh' into hi-pdlp
2 parents 8ba52a3 + b6eeeae commit df32477

File tree

20 files changed

+388
-109
lines changed

20 files changed

+388
-109
lines changed

FEATURES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ Prompted by [#2463](https://github.com/ERGO-Code/HiGHS/issues/2463), the HiGHS s
1414

1515
Only for LPs is there a choice of solver. Previously, when setting the `solver` option to anything other than "choose", any incumbent model was solved as an LP, using that LP solver. This has caused confusiuon for users, and is unnecessary now that there is the `solve_relaxation` option. Now, if the incumbent model is a QP or MIP, it is solved as such (unless `solve_relaxation` is true for a MIP), and the value of the `solver` option only determines what solver is used to solve an LP. If the value of `solver` is "choose", then HiGHS will use what it expects to be the best solver for the problem; if value of `solver` is "ipm", then HiGHS will use what it expects to be the better IPM solver (of HiPO and IPX) for the problem; if value of `solver` is "hipo", then HiGHS will use the HiPO IPM solver (if available in the build); if value of `solver` is "ipx", then HiGHS will use the IPX IPM solver; if value of `solver` is "pdlp", then HiGHS will use the PDLP first-order solver. The option `mip_lp_solver` has been introduced to define which LP solver is used when solving LPs in the MIP solver for which an advanced basis is not known - typically the "root node" LP. Note that The PDLP solver cannot be used to solve such LPs, since it does not yield a basic solution. If an interior point solver fails to obtain a basic solution, the simplex solver will then be used. The option `mip_ipm_solver` has been introduced to define which IPM solver is used when solving LPs in the MIP solver for which IPM is mandatory - typically the analytic centre calculation. When LPs are to be solved by an IPM solver, the HiPO solver is used (if available in the build) unless IPX has been specified explicitly.
1616

17+
As per [#2487](https://github.com/ERGO-Code/HiGHS/issues/2487), trivial heuristics now run before feasibility jump (FJ), and FJ will use any existing incumbent. FJ will clip any finite variable values in the incumbent to lower and upper bounds, and falls back to the existing logic (lower bound if finite, else upper bound if finite, else 0) for any infinite values in the incumbent.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ linear optimization problems of the form
4646

4747
$$ \min \quad \dfrac{1}{2}x^TQx + c^Tx \qquad \textrm{s.t.}~ \quad L \leq Ax \leq U; \quad l \leq x \leq u $$
4848

49-
where Q must be positive semi-definite and, if Q is zero, there may be a requirement that some of the variables take integer values. Thus HiGHS can solve linear programming (LP) problems, convex quadratic programming (QP) problems, and mixed integer programming (MIP) problems. It is mainly written in C++, but also has some C. It has been developed and tested on various Linux, MacOS and Windows installations. No third-party dependencies are required.
49+
where $Q$ must be positive semi-definite and, if $Q$ is zero, there may be a requirement that some of the variables take integer values. Thus HiGHS can solve linear programming (LP) problems, convex quadratic programming (QP) problems, and mixed integer programming (MIP) problems. It is mainly written in C++, but also has some C. It has been developed and tested on various Linux, MacOS and Windows installations. No third-party dependencies are required.
5050

5151
HiGHS has primal and dual revised simplex solvers, originally written by Qi Huangfu and further developed by Julian Hall. It also has an interior point solver for LP written by Lukas Schork, an active set solver for QP written by Michael Feldmeier, and a MIP solver written by Leona Gottwald. Other features have been added by Julian Hall and Ivet Galabova, who manages the software engineering of HiGHS and interfaces to C, C#, FORTRAN, Julia and Python.
5252

check/TestBasis.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,3 +315,43 @@ void testBasisRestart(Highs& highs, const std::string& basis_file,
315315

316316
REQUIRE(info.simplex_iteration_count == 0);
317317
}
318+
319+
TEST_CASE("Basis-read", "[highs_basis_data]") {
320+
// Duplicates test_read_basis in test_highspy.py
321+
const std::string test_name = Catch::getResultCapture().getCurrentTestName();
322+
323+
HighsLp lp;
324+
lp.num_col_ = 2;
325+
lp.num_row_ = 2;
326+
lp.col_cost_ = {0, 1};
327+
lp.col_lower_.assign(lp.num_col_, -kHighsInf);
328+
lp.col_upper_.assign(lp.num_col_, kHighsInf);
329+
lp.row_lower_ = {2, 0};
330+
lp.row_upper_.assign(lp.num_row_, kHighsInf);
331+
lp.a_matrix_.start_ = {0, 2, 4};
332+
lp.a_matrix_.index_ = {0, 1, 0, 1};
333+
lp.a_matrix_.value_ = {-1, 1, 1, 1};
334+
335+
HighsBasisStatus status_before = HighsBasisStatus::kNonbasic;
336+
HighsBasisStatus status_after = HighsBasisStatus::kBasic;
337+
Highs h1;
338+
const HighsBasis& basis1 = h1.getBasis();
339+
h1.passModel(lp);
340+
REQUIRE(basis1.col_status[0] == status_before);
341+
h1.run();
342+
REQUIRE(basis1.col_status[0] == status_after);
343+
344+
Highs h2;
345+
const HighsBasis& basis2 = h2.getBasis();
346+
h2.passModel(lp);
347+
REQUIRE(basis2.col_status[0] == status_before);
348+
349+
const std::string basis_file = test_name + ".bas";
350+
h1.writeBasis(basis_file);
351+
h2.readBasis(basis_file);
352+
REQUIRE(basis2.col_status[0] == status_after);
353+
354+
std::remove(basis_file.c_str());
355+
h1.resetGlobalScheduler(true);
356+
h2.resetGlobalScheduler(true);
357+
}

check/TestIpm.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,3 +208,19 @@ TEST_CASE("test-2087", "[highs_ipm]") {
208208

209209
h.resetGlobalScheduler(true);
210210
}
211+
212+
TEST_CASE("test-2527", "[highs_ipm]") {
213+
std::string filename =
214+
std::string(HIGHS_DIR) + "/check/instances/primal1.mps";
215+
Highs h;
216+
// h.setOptionValue("output_flag", dev_run);
217+
REQUIRE(h.readModel(filename) == HighsStatus::kOk);
218+
HighsLp lp = h.getLp();
219+
lp.col_cost_.assign(lp.num_col_, 0);
220+
REQUIRE(h.passModel(lp) == HighsStatus::kOk);
221+
h.setOptionValue("solver", kIpmString);
222+
h.setOptionValue("presolve", kHighsOffString);
223+
REQUIRE(h.run() == HighsStatus::kOk);
224+
225+
h.resetGlobalScheduler(true);
226+
}

highs/interfaces/highs_c_api.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,9 @@ HighsInt Highs_presolve(void* highs);
400400
HighsInt Highs_run(void* highs);
401401

402402
/**
403-
* Postsolve a model using a primal (and possibly dual) solution.
403+
* Postsolve a model using a primal (and possibly dual) solution. The
404+
* postsolved solution can be retrieved later by calling
405+
* `Highs_getSolution`.
404406
*
405407
* @param highs A pointer to the Highs instance.
406408
* @param col_value An array of length [num_col] with the column solution

highs/ipm/ipx/basis.cc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,12 +119,21 @@ Int Basis::Factorize() {
119119

120120
// Build column pointers for passing to LU factorization.
121121
std::vector<Int> begin(m), end(m);
122+
Int basis_num_nz = 0;
122123
for (Int i = 0; i < m; i++) {
123124
assert(basis_[i] >= 0);
124125
begin[i] = AI.begin(basis_[i]);
125126
end[i] = AI.end(basis_[i]);
127+
basis_num_nz += (end[i]-begin[i]);
126128
}
127129

130+
std::stringstream h_logging_stream;
131+
h_logging_stream.str(std::string());
132+
h_logging_stream <<
133+
" Start factorization " << num_factorizations_+1 <<
134+
": nonzeros in basis = " << basis_num_nz << "\n";
135+
control_.hIntervalLog(h_logging_stream);
136+
128137
Int err = 0; // return code
129138
while (true) {
130139
Int flag = lu_->Factorize(begin.data(), end.data(), AI.rowidx(),
@@ -151,6 +160,11 @@ Int Basis::Factorize() {
151160
}
152161
time_factorize_ += timer.Elapsed();
153162
factorization_is_fresh_ = true;
163+
h_logging_stream.str(std::string());
164+
h_logging_stream <<
165+
" Finish factorization " << num_factorizations_ <<
166+
": fill factor = " << lu_->fill_factor() << "\n";
167+
control_.hIntervalLog(h_logging_stream);
154168
return err;
155169
}
156170

highs/ipm/ipx/ipm.cc

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -822,13 +822,15 @@ void IPM::PrintHeader() {
822822
std::stringstream h_logging_stream;
823823
h_logging_stream.str(std::string());
824824
h_logging_stream
825-
<< (kTerminationLogging ? "\n" : "")
826-
<< " " << Format("Iter", 4)
827-
<< " " << Format("P.res", 8) << " " << Format("D.res", 8)
828-
<< " " << Format("P.obj", 15) << " " << Format("D.obj", 15)
829-
<< " " << Format("mu", 8);
825+
<< " " << Format("Iter", 4)
826+
<< " " << Format("primal obj", 15)
827+
<< " " << Format("dual obj", 15)
828+
<< " " << Format("pinf", 9)
829+
<< " " << Format("dinf", 9)
830+
<< " " << Format("gap", 8);
831+
// h_logging_stream << " " << Format("mu", 8);
830832
if (!control_.timelessLog())
831-
h_logging_stream << " " << Format("Time", 7);
833+
h_logging_stream << " " << Format("time", 7);
832834
control_.hLog(h_logging_stream);
833835
control_.Debug()
834836
<< " " << Format("stepsizes", 9)
@@ -842,17 +844,29 @@ void IPM::PrintHeader() {
842844
void IPM::PrintOutput() {
843845
const bool ipm_optimal = iterate_->feasible() && iterate_->optimal();
844846

845-
if (kTerminationLogging) PrintHeader();
847+
double logging_pobj = iterate_->pobjective_after_postproc();
848+
double logging_dobj = iterate_->dobjective_after_postproc();
849+
double logging_presidual = iterate_->presidual();
850+
double logging_dresidual = iterate_->dresidual();
851+
852+
// Now logging relative primal and dual infeasibility, and also
853+
// the relative primal dual objective gap
854+
logging_presidual /= iterate_->bounds_measure_;
855+
logging_dresidual /= iterate_->costs_measure_;
856+
double logging_gap = std::abs(logging_pobj - logging_dobj) /
857+
(1.0+0.5 *std::fabs(logging_pobj + logging_dobj));
858+
846859
std::stringstream h_logging_stream;
847860
h_logging_stream.str(std::string());
848861
h_logging_stream
849862
<< " " << Format(info_->iter, 3)
850863
<< (ipm_optimal ? "*" : " ")
851-
<< " " << Scientific(iterate_->presidual(), 8, 2)
852-
<< " " << Scientific(iterate_->dresidual(), 8, 2)
853-
<< " " << Scientific(iterate_->pobjective_after_postproc(), 15, 8)
854-
<< " " << Scientific(iterate_->dobjective_after_postproc(), 15, 8)
855-
<< " " << Scientific(iterate_->mu(), 8, 2);
864+
<< " " << Scientific(logging_pobj, 15, 8)
865+
<< " " << Scientific(logging_dobj, 15, 8)
866+
<< " " << Scientific(logging_presidual, 9, 2)
867+
<< " " << Scientific(logging_dresidual, 9, 2)
868+
<< " " << Scientific(logging_gap, 8, 2);
869+
// h_logging_stream << " " << Scientific(iterate_->mu(), 8, 2);
856870
if (!control_.timelessLog())
857871
h_logging_stream << " " << Fixed(control_.Elapsed(), 6, 0) << "s";
858872
control_.hLog(h_logging_stream);

highs/ipm/ipx/iterate.cc

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ Iterate::Iterate(const Model& model) : model_(model) {
5555
}
5656
}
5757
assert_consistency();
58+
this->bounds_measure_ = 1.0 + model_.norm_bounds();
59+
this->costs_measure_ = 1.0 + model_.norm_c();
60+
5861
}
5962

6063
void Iterate::Initialize(const Vector& x, const Vector& xl, const Vector& xu,
@@ -219,40 +222,21 @@ double Iterate::mu_max() const { Evaluate(); return mu_max_; }
219222

220223
bool Iterate::feasible() const {
221224
Evaluate();
222-
const double bounds_measure = 1.0 + model_.norm_bounds();
223-
const double costs_measure = 1.0 + model_.norm_c();
224-
const double rel_presidual = presidual_ / bounds_measure;
225-
const double rel_dresidual = dresidual_ / costs_measure;
226-
const bool primal_feasible = presidual_ <= feasibility_tol_ * (bounds_measure);
227-
const bool dual_feasible = dresidual_ <= feasibility_tol_ * (costs_measure);
225+
const bool primal_feasible = presidual_ <= feasibility_tol_ * bounds_measure_;
226+
const bool dual_feasible = dresidual_ <= feasibility_tol_ * costs_measure_;
228227
const bool is_feasible = primal_feasible && dual_feasible;
229-
if (kTerminationLogging) {
230-
printf("\nIterate::feasible presidual_ = %11.4g; bounds_measure = %11.4g; "
231-
"rel_presidual = %11.4g; feasibility_tol = %11.4g: primal_feasible = %d\n",
232-
presidual_, bounds_measure, rel_presidual, feasibility_tol_, primal_feasible);
233-
printf("Iterate::feasible dresidual_ = %11.4g; costs_measure = %11.4g; "
234-
"rel_dresidual = %11.4g; feasibility_tol = %11.4g: dual_feasible = %d\n",
235-
dresidual_, costs_measure, rel_dresidual, feasibility_tol_, dual_feasible);
236-
}
237228
return is_feasible;
238229
}
239230

240231
bool Iterate::optimal() const {
241232
Evaluate();
242233
double pobj = pobjective_after_postproc();
243234
double dobj = dobjective_after_postproc();
244-
double obj = 0.5 * (pobj + dobj);
235+
double ave_obj = 0.5 * (pobj + dobj);
245236
double gap = pobj - dobj;
246237
const double abs_gap = std::abs(gap);
247-
const double obj_measure = 1.0+std::abs(obj);
238+
const double obj_measure = 1.0+std::abs(ave_obj);
248239
const bool is_optimal = abs_gap <= optimality_tol_ * obj_measure;
249-
if (kTerminationLogging) {
250-
const double rel_gap = abs_gap / obj_measure;
251-
printf("Iterate::optimal abs_gap = %11.4g;"
252-
" obj_measure = %11.4g; rel_gap = %11.4g;"
253-
" optimality_tol = %11.4g: optimal = %d\n",
254-
abs_gap, obj_measure, rel_gap, optimality_tol_, is_optimal);
255-
}
256240
return is_optimal;
257241
}
258242

highs/ipm/ipx/iterate.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,9 @@ class Iterate {
199199
// The method can only be called after Postprocess().
200200
void DropToComplementarity(Vector& x, Vector& y, Vector& z) const;
201201

202+
double bounds_measure_;
203+
double costs_measure_;
204+
202205
private:
203206
// A (primal or dual) variable that is required to be positive in the IPM is
204207
// not moved closer to zero than kBarrierMin.

highs/ipm/ipx/lp_solver.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -522,8 +522,8 @@ void LpSolver::BuildStartingBasis() {
522522
info_.status_ipm = IPX_STATUS_debug;
523523
return;
524524
}
525-
basis_.reset(new Basis(control_, model_));
526525
control_.hLog(" Constructing starting basis...\n");
526+
basis_.reset(new Basis(control_, model_));
527527
StartingBasis(iterate_.get(), basis_.get(), &info_);
528528
if (info_.errflag == IPX_ERROR_user_interrupt) {
529529
info_.errflag = 0;

0 commit comments

Comments
 (0)