Skip to content

Commit 08b4740

Browse files
authored
Merge branch 'latest' into strong-branch-refactor
2 parents e8156f2 + 0859510 commit 08b4740

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1928
-891
lines changed

FEATURES.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,7 @@ The irreducible infeasibility system (IIS) facility now detects infeasibility du
1212

1313
Prompted by [#2463](https://github.com/ERGO-Code/HiGHS/issues/2463), the HiGHS solution and basis files now match data to any column and row names in the model, only assuming that the data are aligned with column and row indices if there are no names in the model. This requires a new version (v2) of the HiGHS basis file. Basis files from v1 are still read, but deprecated. Now, when writing out a model, basis or solution, column and row names are added to the model - previously they were created temporarily and inconsistentyly on the fly. If the model has existing names, then distinctive names are created to replace any blank names, but names with spaces or duplicate names yield an error status return.
1414

15-
Refactored strong branching to minimize duplicated code
15+
Refactored strong branching to minimize duplicated code
16+
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.
18+

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/TestCallbacks.cpp

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ struct MipData {
3636
struct UserMipSolution {
3737
double optimal_objective_value;
3838
std::vector<double> optimal_solution;
39-
HighsInt require_user_solution_callback_origin;
39+
HighsInt require_external_solution_query_origin;
4040
};
4141

4242
// Callback that saves message for comparison
@@ -193,8 +193,8 @@ HighsCallbackFunctionType userkMipUserSolution =
193193
void* user_callback_data) {
194194
UserMipSolution callback_data =
195195
*(static_cast<UserMipSolution*>(user_callback_data));
196-
if (data_out->user_solution_callback_origin ==
197-
callback_data.require_user_solution_callback_origin) {
196+
if (data_out->external_solution_query_origin ==
197+
callback_data.require_external_solution_query_origin) {
198198
if (data_out->mip_primal_bound >
199199
callback_data.optimal_objective_value) {
200200
// If current objective value is not optimal, pass the
@@ -203,7 +203,7 @@ HighsCallbackFunctionType userkMipUserSolution =
203203
printf(
204204
"userkMipUserSolution: origin = %d; %g = mip_primal_bound > "
205205
"optimal_objective_value = %g\n",
206-
int(data_out->user_solution_callback_origin),
206+
int(data_out->external_solution_query_origin),
207207
data_out->mip_primal_bound,
208208
callback_data.optimal_objective_value);
209209
data_in->user_has_solution = true;
@@ -218,13 +218,13 @@ HighsCallbackFunctionType userkMipUserSetSolution =
218218
void* user_callback_data) {
219219
const auto& callback_data =
220220
*(static_cast<UserMipSolution*>(user_callback_data));
221-
if (data_out->user_solution_callback_origin ==
222-
callback_data.require_user_solution_callback_origin) {
221+
if (data_out->external_solution_query_origin ==
222+
callback_data.require_external_solution_query_origin) {
223223
if (dev_run)
224224
printf(
225225
"userkMipUserSetSolution: origin = %d; %g = mip_primal_bound > "
226226
"optimal_objective_value = %g\n",
227-
int(data_out->user_solution_callback_origin),
227+
int(data_out->external_solution_query_origin),
228228
data_out->mip_primal_bound,
229229
callback_data.optimal_objective_value);
230230

@@ -239,14 +239,14 @@ HighsCallbackFunctionType userkMipUserSetPartialSolution =
239239
void* user_callback_data) {
240240
const auto& callback_data =
241241
*(static_cast<UserMipSolution*>(user_callback_data));
242-
if (data_out->user_solution_callback_origin ==
243-
callback_data.require_user_solution_callback_origin) {
242+
if (data_out->external_solution_query_origin ==
243+
callback_data.require_external_solution_query_origin) {
244244
if (dev_run)
245245
printf(
246246
"userkMipUserSetPartialSolution: origin = %d; %g = "
247247
"mip_primal_bound > "
248248
"optimal_objective_value = %g\n",
249-
int(data_out->user_solution_callback_origin),
249+
int(data_out->external_solution_query_origin),
250250
data_out->mip_primal_bound,
251251
callback_data.optimal_objective_value);
252252

@@ -511,7 +511,7 @@ static void runMipUserSolutionTest(
511511
UserMipSolution user_callback_data;
512512
user_callback_data.optimal_objective_value = objective_function_value0;
513513
user_callback_data.optimal_solution = optimal_solution;
514-
user_callback_data.require_user_solution_callback_origin =
514+
user_callback_data.require_external_solution_query_origin =
515515
require_origin[iModel];
516516
void* p_user_callback_data = (void*)(&user_callback_data);
517517

@@ -597,4 +597,4 @@ TEST_CASE("highs-callback-mip-user-solution-c", "[highs-callback]") {
597597

598598
highs.run();
599599
highs.resetGlobalScheduler(true);
600-
}
600+
}

check/TestIpm.cpp

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

201201
h.resetGlobalScheduler(true);
202202
}
203+
204+
TEST_CASE("test-2527", "[highs_ipm]") {
205+
std::string filename =
206+
std::string(HIGHS_DIR) + "/check/instances/primal1.mps";
207+
Highs h;
208+
// h.setOptionValue("output_flag", dev_run);
209+
REQUIRE(h.readModel(filename) == HighsStatus::kOk);
210+
HighsLp lp = h.getLp();
211+
lp.col_cost_.assign(lp.num_col_, 0);
212+
REQUIRE(h.passModel(lp) == HighsStatus::kOk);
213+
h.setOptionValue("solver", kIpmString);
214+
h.setOptionValue("presolve", kHighsOffString);
215+
REQUIRE(h.run() == HighsStatus::kOk);
216+
217+
h.resetGlobalScheduler(true);
218+
}

check/TestMipSolver.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,16 @@ TEST_CASE("MIP-rowless-1", "[highs_test_mip_solver]") {
4040
Highs highs;
4141
if (!dev_run) highs.setOptionValue("output_flag", false);
4242
rowlessMIP1(highs);
43+
44+
highs.resetGlobalScheduler(true);
4345
}
4446

4547
TEST_CASE("MIP-rowless-2", "[highs_test_mip_solver]") {
4648
Highs highs;
4749
if (!dev_run) highs.setOptionValue("output_flag", false);
4850
rowlessMIP2(highs);
51+
52+
highs.resetGlobalScheduler(true);
4953
}
5054

5155
TEST_CASE("MIP-solution-limit", "[highs_test_mip_solver]") {
@@ -79,6 +83,8 @@ TEST_CASE("MIP-solution-limit", "[highs_test_mip_solver]") {
7983
REQUIRE(highs.getModelStatus() == HighsModelStatus::kSolutionLimit);
8084
highs.setOptionValue("mip_max_improving_sols", kHighsIInf);
8185
highs.clearSolver();
86+
87+
highs.resetGlobalScheduler(true);
8288
}
8389

8490
TEST_CASE("MIP-integrality", "[highs_test_mip_solver]") {
@@ -173,6 +179,8 @@ TEST_CASE("MIP-integrality", "[highs_test_mip_solver]") {
173179
REQUIRE(info.mip_node_count == 1);
174180
REQUIRE(fabs(info.mip_dual_bound + 6) < double_equal_tolerance);
175181
REQUIRE(std::fabs(info.mip_gap) < 1e-12);
182+
183+
highs.resetGlobalScheduler(true);
176184
}
177185

178186
TEST_CASE("MIP-clear-integrality", "[highs_test_mip_solver]") {
@@ -215,6 +223,8 @@ TEST_CASE("MIP-nmck", "[highs_test_mip_solver]") {
215223
REQUIRE(info.num_primal_infeasibilities == 0);
216224
REQUIRE(info.max_primal_infeasibility == 0);
217225
REQUIRE(info.sum_primal_infeasibilities == 0);
226+
227+
highs.resetGlobalScheduler(true);
218228
}
219229

220230
TEST_CASE("MIP-maximize", "[highs_test_mip_solver]") {
@@ -295,6 +305,8 @@ TEST_CASE("MIP-maximize", "[highs_test_mip_solver]") {
295305
REQUIRE(std::abs(info.objective_function_value - info.mip_dual_bound) <=
296306
options.mip_abs_gap);
297307
REQUIRE(std::abs(info.mip_gap) <= options.mip_rel_gap);
308+
309+
highs.resetGlobalScheduler(true);
298310
}
299311

300312
TEST_CASE("MIP-unbounded", "[highs_test_mip_solver]") {
@@ -403,6 +415,8 @@ TEST_CASE("MIP-unbounded", "[highs_test_mip_solver]") {
403415

404416
model_status = highs.getModelStatus();
405417
REQUIRE(model_status == HighsModelStatus::kInfeasible);
418+
419+
highs.resetGlobalScheduler(true);
406420
}
407421

408422
TEST_CASE("MIP-od", "[highs_test_mip_solver]") {
@@ -470,6 +484,8 @@ TEST_CASE("MIP-od", "[highs_test_mip_solver]") {
470484
double_equal_tolerance);
471485
REQUIRE(fabs(solution.col_value[0] - required_x0_value) <
472486
double_equal_tolerance);
487+
488+
highs.resetGlobalScheduler(true);
473489
}
474490

475491
TEST_CASE("MIP-infeasible-start", "[highs_test_mip_solver]") {
@@ -513,6 +529,8 @@ TEST_CASE("MIP-infeasible-start", "[highs_test_mip_solver]") {
513529
HighsStatus::kOk);
514530
highs.run();
515531
REQUIRE(model_status == HighsModelStatus::kInfeasible);
532+
533+
highs.resetGlobalScheduler(true);
516534
}
517535

518536
TEST_CASE("get-integrality", "[highs_test_mip_solver]") {}
@@ -556,6 +574,8 @@ TEST_CASE("MIP-bounds", "[highs_test_mip_solver]") {
556574
obj1);
557575
REQUIRE(obj0 == obj1);
558576
std::remove(test_mps.c_str());
577+
578+
highs.resetGlobalScheduler(true);
559579
}
560580

561581
TEST_CASE("MIP-get-saved-solutions", "[highs_test_mip_solver]") {
@@ -583,6 +603,8 @@ TEST_CASE("MIP-get-saved-solutions", "[highs_test_mip_solver]") {
583603
REQUIRE(saved_objective_and_solution[last_saved_solution].col_value[iCol] ==
584604
highs.getSolution().col_value[iCol]);
585605
std::remove(solution_file.c_str());
606+
607+
highs.resetGlobalScheduler(true);
586608
}
587609

588610
TEST_CASE("MIP-objective-target", "[highs_test_mip_solver]") {
@@ -597,6 +619,8 @@ TEST_CASE("MIP-objective-target", "[highs_test_mip_solver]") {
597619
highs.run();
598620
REQUIRE(highs.getModelStatus() == HighsModelStatus::kObjectiveTarget);
599621
REQUIRE(highs.getInfo().objective_function_value > egout_optimal_objective);
622+
623+
highs.resetGlobalScheduler(true);
600624
}
601625

602626
TEST_CASE("MIP-max-offset-test", "[highs_test_mip_solver]") {
@@ -625,6 +649,8 @@ TEST_CASE("MIP-max-offset-test", "[highs_test_mip_solver]") {
625649
highs.getInfo().objective_function_value;
626650
REQUIRE(objectiveOk(max_offset_optimal_objective, -offset_optimal_objective,
627651
dev_run));
652+
653+
highs.resetGlobalScheduler(true);
628654
}
629655

630656
TEST_CASE("MIP-get-saved-solutions-presolve", "[highs_test_mip_solver]") {
@@ -663,6 +689,8 @@ TEST_CASE("MIP-get-saved-solutions-presolve", "[highs_test_mip_solver]") {
663689
REQUIRE(saved_objective_and_solution[last_saved_solution].col_value[iCol] ==
664690
highs.getSolution().col_value[iCol]);
665691
std::remove(solution_file.c_str());
692+
693+
highs.resetGlobalScheduler(true);
666694
}
667695

668696
TEST_CASE("IP-infeasible-unbounded", "[highs_test_mip_solver]") {
@@ -720,6 +748,8 @@ TEST_CASE("IP-infeasible-unbounded", "[highs_test_mip_solver]") {
720748
}
721749
highs.setOptionValue("presolve", kHighsOnString);
722750
}
751+
752+
highs.resetGlobalScheduler(true);
723753
}
724754

725755
TEST_CASE("IP-with-fract-bounds-no-presolve", "[highs_test_mip_solver]") {
@@ -755,6 +785,8 @@ TEST_CASE("IP-with-fract-bounds-no-presolve", "[highs_test_mip_solver]") {
755785

756786
// Infeasible
757787
REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible);
788+
789+
highs.resetGlobalScheduler(true);
758790
}
759791

760792
bool objectiveOk(const double optimal_objective,
@@ -787,6 +819,8 @@ void solve(Highs& highs, std::string presolve,
787819
require_optimal_objective, dev_run));
788820
}
789821
REQUIRE(highs.resetOptions() == HighsStatus::kOk);
822+
823+
highs.resetGlobalScheduler(true);
790824
}
791825

792826
void distillationMIP(Highs& highs) {

check/TestPresolve.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,32 @@ TEST_CASE("presolve-issue-2446", "[highs_test_presolve]") {
735735
REQUIRE(highs.getModelPresolveStatus() == HighsPresolveStatus::kReduced);
736736
}
737737

738+
TEST_CASE("presolve-solve-postsolve-no-col-dual", "[highs_test_presolve]") {
739+
Highs highs;
740+
highs.setOptionValue("output_flag", dev_run);
741+
std::string model_file =
742+
std::string(HIGHS_DIR) + "/check/instances/afiro.mps";
743+
highs.readModel(model_file);
744+
highs.presolve();
745+
HighsLp presolved_lp = highs.getPresolvedLp();
746+
Highs highs1;
747+
highs1.setOptionValue("output_flag", dev_run);
748+
highs1.setOptionValue("presolve", kHighsOffString);
749+
highs1.passModel(presolved_lp);
750+
highs1.run();
751+
HighsSolution solution = highs1.getSolution();
752+
753+
// Perform postsolve using the optimal solution and basis for the
754+
// presolved model
755+
REQUIRE(highs.postsolve(solution) == HighsStatus::kOk);
756+
757+
// If row duals are supplied, then column duals must also be suppplied
758+
solution.col_dual.clear();
759+
REQUIRE(highs.postsolve(solution) == HighsStatus::kError);
760+
761+
highs.resetGlobalScheduler(true);
762+
}
763+
738764
TEST_CASE("presolve-egout-ac", "[highs_test_presolve]") {
739765
// Tests the case where, for this model when run_crossover is off,
740766
// sparsify is used to reduce the LP to empty. However, when
@@ -812,3 +838,27 @@ TEST_CASE("presolve-egout-ac", "[highs_test_presolve]") {
812838

813839
h.resetGlobalScheduler(true);
814840
}
841+
842+
TEST_CASE("dual-bound-tightening", "[highs_test_presolve]") {
843+
std::string model_file =
844+
std::string(HIGHS_DIR) + "/check/instances/gesa2.mps";
845+
846+
Highs highs;
847+
highs.setOptionValue("output_flag", dev_run);
848+
highs.readModel(model_file);
849+
850+
// complement variables to get code coverage
851+
HighsLp lp = highs.getLp();
852+
std::transform(lp.a_matrix_.value_.begin(), lp.a_matrix_.value_.end(),
853+
lp.a_matrix_.value_.begin(), [](double v) { return -v; });
854+
std::transform(lp.col_cost_.begin(), lp.col_cost_.end(), lp.col_cost_.begin(),
855+
[](double v) { return -v; });
856+
std::transform(lp.col_upper_.begin(), lp.col_upper_.end(),
857+
lp.col_upper_.begin(), [](double v) { return -v; });
858+
std::transform(lp.col_lower_.begin(), lp.col_lower_.end(),
859+
lp.col_lower_.begin(), [](double v) { return -v; });
860+
std::swap(lp.col_lower_, lp.col_upper_);
861+
862+
highs.passModel(lp);
863+
REQUIRE(highs.presolve() == HighsStatus::kOk);
864+
}

check/TestSemiVariables.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ TEST_CASE("semi-variable-model", "[highs_test_semi_variables]") {
1717
const HighsInfo& info = highs.getInfo();
1818
HighsStatus return_status;
1919
double optimal_objective_function_value;
20-
if (!dev_run) highs.setOptionValue("output_flag", false);
20+
highs.setOptionValue("output_flag", dev_run);
2121
HighsModel model;
2222
HighsLp& lp = model.lp_;
2323
semiModel0(lp);

highs/Highs.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1700,7 +1700,7 @@ class Highs {
17001700
bool qFormatOk(const HighsInt num_nz, const HighsInt format);
17011701
void clearZeroHessian();
17021702
HighsStatus checkOptimality(const std::string& solver_type);
1703-
HighsStatus lpKktCheck(const std::string& message);
1703+
HighsStatus lpKktCheck(const HighsLp& lp, const std::string& message = "");
17041704
HighsStatus invertRequirementError(std::string method_name) const;
17051705

17061706
HighsStatus handleInfCost();

0 commit comments

Comments
 (0)