Skip to content

Commit 8093b10

Browse files
committed
Merge branch 'latest' of https://github.com/ERGO-Code/HiGHS into affinity
2 parents 48362f8 + 15d7f80 commit 8093b10

18 files changed

Lines changed: 314 additions & 99 deletions

check/TestCheckSolution.cpp

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,9 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") {
7878
std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps";
7979
Highs highs;
8080
const HighsInfo& info = highs.getInfo();
81-
if (dev_run) printf("\n********************\nSolving from scratch\n");
81+
if (dev_run)
82+
printf(
83+
"\n********************\nSolving from scratch\n********************\n");
8284
highs.setOptionValue("output_flag", dev_run);
8385
highs.readModel(model_file);
8486
HighsLp lp = highs.getLp();
@@ -92,7 +94,7 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") {
9294

9395
std::string solution_file = test_name + model + ".sol";
9496
if (dev_run) REQUIRE(highs.writeSolution("") == HighsStatus::kOk);
95-
;
97+
9698
REQUIRE(highs.writeSolution(solution_file) == HighsStatus::kOk);
9799

98100
highs.clear();
@@ -102,7 +104,9 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") {
102104
bool valid, integral, feasible;
103105
if (test0) {
104106
if (dev_run)
105-
printf("\n***************************\nSolving from saved solution\n");
107+
printf(
108+
"\n***************************\nSolving from saved "
109+
"solution\n***************************\n");
106110
highs.setOptionValue("output_flag", dev_run);
107111
highs.readModel(model_file);
108112

@@ -124,7 +128,9 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") {
124128
const bool test1 = other_tests;
125129
if (test1) {
126130
if (dev_run)
127-
printf("\n***************************\nSolving from solution file\n");
131+
printf(
132+
"\n**************************\nSolving from solution "
133+
"file\n**************************\n");
128134
highs.setOptionValue("output_flag", dev_run);
129135
highs.readModel(model_file);
130136

@@ -147,8 +153,8 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") {
147153
if (test2) {
148154
if (dev_run)
149155
printf(
150-
"\n***************************\nSolving from saved integer "
151-
"solution\n");
156+
"\n***********************************\nSolving from saved integer "
157+
"solution\n***********************************\n");
152158
highs.setOptionValue("output_flag", dev_run);
153159
highs.readModel(model_file);
154160

@@ -180,7 +186,8 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") {
180186
if (test3) {
181187
if (dev_run)
182188
printf(
183-
"\n***************************\nSolving from column solution file\n");
189+
"\n*********************************\nSolving from column solution "
190+
"file\n*********************************\n");
184191
std::string column_solution_file =
185192
std::string(HIGHS_DIR) + "/check/instances/flugpl_integer.sol";
186193

@@ -206,8 +213,9 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") {
206213
if (test4) {
207214
if (dev_run)
208215
printf(
209-
"\n***************************\nSolving from illegal column solution "
210-
"file\n");
216+
"\n*****************************************\nSolving from illegal "
217+
"column solution "
218+
"file\n*****************************************\n");
211219
std::string column_solution_file =
212220
std::string(HIGHS_DIR) + "/check/instances/flugpl_illegal_integer.sol";
213221

@@ -225,8 +233,9 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") {
225233
HighsSolution starting_solution = optimal_solution;
226234
if (dev_run)
227235
printf(
228-
"\n***************************\nSolving from partial integer "
229-
"solution\n");
236+
"\n*************************************\nSolving from partial "
237+
"integer "
238+
"solution\n*************************************\n");
230239
highs.setOptionValue("output_flag", dev_run);
231240
highs.readModel(model_file);
232241

@@ -256,8 +265,8 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") {
256265
if (test6) {
257266
if (dev_run)
258267
printf(
259-
"\n***************************\nSolving from sparse integer "
260-
"solution\n");
268+
"\n************************************\nSolving from sparse integer "
269+
"solution\n************************************\n");
261270
HighsInt num_integer_variable = 0;
262271
for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++)
263272
if (lp.integrality_[iCol] == HighsVarType::kInteger)

check/TestMipSolver.cpp

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1483,3 +1483,125 @@ TEST_CASE("issue-2975", "[highs_test_mip_solver]") {
14831483

14841484
highs.resetGlobalScheduler(true);
14851485
}
1486+
1487+
TEST_CASE("issue-3118", "[highs_test_mip_solver]") {
1488+
const double M = 1e10;
1489+
// min x + y
1490+
// s.t. x + M*y = 1
1491+
// M*x + y = 1
1492+
// x, y binary
1493+
// Initial "solution" x = y = 1/M
1494+
//
1495+
// This problem is found infeasible in presolve, but the point x = y
1496+
// = 1/M is integer feasible to within the tolerances, so is
1497+
// acccepted as an optimal solution to the problem
1498+
Highs highs;
1499+
highs.setOptionValue("output_flag", dev_run);
1500+
1501+
HighsInt x = 0;
1502+
HighsInt y = 1;
1503+
HighsLp lp;
1504+
lp.num_col_ = 2;
1505+
lp.num_row_ = 2;
1506+
lp.col_lower_ = {0., 0.};
1507+
lp.col_upper_ = {1., 1.};
1508+
lp.col_cost_ = {1., 1.};
1509+
lp.integrality_ = {HighsVarType::kInteger, HighsVarType::kInteger};
1510+
lp.row_lower_ = {1., 1.};
1511+
lp.row_upper_ = {1., 1.};
1512+
lp.a_matrix_.format_ = MatrixFormat::kRowwise;
1513+
lp.a_matrix_.start_ = {0, 2, 4};
1514+
lp.a_matrix_.index_ = {x, y, x, y};
1515+
lp.a_matrix_.value_ = {1., M, M, 1};
1516+
highs.passModel(lp);
1517+
1518+
std::vector<double> solution_values(lp.num_col_, 1 / M);
1519+
highs.setSolution(2, nullptr, solution_values.data());
1520+
1521+
highs.run();
1522+
REQUIRE(highs.getModelStatus() == HighsModelStatus::kOptimal);
1523+
REQUIRE(std::abs(2 / M - highs.getInfo().objective_function_value) < 1e-9);
1524+
if (dev_run) highs.writeSolution("", 1);
1525+
1526+
highs.resetGlobalScheduler(true);
1527+
}
1528+
1529+
TEST_CASE("issue-3118a", "[highs_test_mip_solver]") {
1530+
const double M = 1e10;
1531+
// max f = x
1532+
// s.t. x - M*y = -1
1533+
// x <= b
1534+
// x, y integer in [0, 2]x[0, 1]
1535+
//
1536+
// The MIP is not feasible over the integers for any b
1537+
//
1538+
// With initial "solution" x = 0; y = 1/M
1539+
//
1540+
// For b = 0: (0, 1/M) with f = 0 is the only feasible solution of
1541+
// the relaxation, and it's also feasible for the MIP, so claiming
1542+
// it is optimal when presolve identifies infeasibility is clearly
1543+
// justified
1544+
//
1545+
// For b = 1: (1, 2/M) with f = 1 is the optimal solution of the
1546+
// relaxation, and it's also feasible for the MIP. However, claiming
1547+
// that the initial "solution" (0, 1/M) with f = 0 is optimal when
1548+
// presolve identifies infeasibility is still justified, because
1549+
// it's a point that is feasible for a MIP that is deemed infeasible
1550+
// by presolve
1551+
Highs highs;
1552+
highs.setOptionValue("output_flag", dev_run);
1553+
1554+
HighsInt x = 0;
1555+
HighsInt y = 1;
1556+
HighsLp lp;
1557+
lp.sense_ = ObjSense::kMaximize;
1558+
lp.num_col_ = 2;
1559+
lp.num_row_ = 2;
1560+
lp.col_lower_ = {0, 0};
1561+
lp.col_upper_ = {2, 1};
1562+
lp.col_cost_ = {1, 0};
1563+
lp.integrality_ = {HighsVarType::kInteger, HighsVarType::kInteger};
1564+
lp.row_lower_ = {-1, -kHighsInf};
1565+
lp.row_upper_ = {-1, 0};
1566+
lp.a_matrix_.format_ = MatrixFormat::kRowwise;
1567+
lp.a_matrix_.start_ = {0, 2, 3};
1568+
lp.a_matrix_.index_ = {x, y, x};
1569+
lp.a_matrix_.value_ = {1., -M, 1};
1570+
1571+
for (HighsInt k = 0; k < 2; k++) {
1572+
HighsInt b = k == 0 ? 0 : 1;
1573+
lp.row_upper_[1] = b;
1574+
1575+
highs.passModel(lp);
1576+
1577+
// Solve as MIP
1578+
if (dev_run)
1579+
printf("================\nCase b = %d (MIP)\n================\n", int(b));
1580+
highs.setOptionValue("solve_relaxation", false);
1581+
std::vector<double> solution_values = {0, 1 / M};
1582+
highs.setSolution(2, nullptr, solution_values.data());
1583+
1584+
highs.run();
1585+
if (dev_run) highs.writeSolution("", 1);
1586+
REQUIRE(highs.getModelStatus() == HighsModelStatus::kOptimal);
1587+
1588+
// Solve as LP
1589+
if (dev_run)
1590+
printf("===============\nCase b = %d (LP)\n===============\n", int(b));
1591+
highs.setOptionValue("solve_relaxation", true);
1592+
highs.clearSolver();
1593+
highs.run();
1594+
highs.writeSolution("", 1);
1595+
REQUIRE(highs.getModelStatus() == HighsModelStatus::kOptimal);
1596+
double lp_objective_value = highs.getInfo().objective_function_value;
1597+
1598+
solution_values = highs.getSolution().col_value;
1599+
highs.setSolution(2, nullptr, solution_values.data());
1600+
1601+
bool valid, integral, feasible;
1602+
REQUIRE(highs.assessPrimalSolution(valid, integral, feasible) ==
1603+
HighsStatus::kOk);
1604+
}
1605+
1606+
highs.resetGlobalScheduler(true);
1607+
}

docs/src/callbacks.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ The `HighsCallbackDataOut` struct supplies data to the user that is
154154
relevant to the particular callback. The general data are
155155
156156
* `log_type`: An integer cast of the `HighsLogType` value, indicating the severity of the logging message--relevant to the logging callback.
157-
* `running_time`: The excution time of HiGHS--relevant to the interrupt callbacks.
157+
* `running_time`: The execution time of HiGHS--relevant to the interrupt callbacks.
158158
* `simplex_iteration_count`: The number of simplex iterations performed--relevant to the simplex interrupt callback.
159159
* `ipm_iteration_count`: The number of IPM iterations performed--relevant to the IPM interrupt callback.
160160
* `pdlp_iteration_count`: The number of PDLP iterations performed--relevant to the PDLP interrupt callback.

docs/src/guide/further.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,11 +139,11 @@ lexicographic optimization according to the truth of the
139139
linear objective is represented by the following data, held in the
140140
[HighsLinearObjective](@ref HighsLinearObjective) structure
141141

142-
- weight: Scalar of type double - The weight of this objective when blending
142+
- weight: Scalar of type double - The weight of this objective when blending
143143
- offset: Scalar of type double - The offset of this objective
144144
- coefficients: Vector of type double - The coefficients of this objective
145-
- abs\_tolerance: Scalar of type double - The absolute tolerance on this objective when performing lexicographic optimization
146-
- rel\_tolerance: Scalar of type double - The relative tolerance on this objective when performing lexicographic optimization
145+
- abs\_tolerance: Scalar of type double - The absolute tolerance on this objective when performing lexicographic optimization
146+
- rel\_tolerance: Scalar of type double - The relative tolerance on this objective when performing lexicographic optimization
147147
- priority: Scalar of type HighsInt - The priority of this objective when performing lexicographic optimization
148148

149149
### Methods
@@ -174,7 +174,7 @@ priority values must be distinct_.
174174

175175
* Minimize/maximize with respect to the linear objective of highest priority value, according to whether its `weight` is positive/negative
176176

177-
* Add a constraint to the model so that the value of the linear objective of highest priority satsifies a bound given by the values of `abs_tolerance` and/or `rel_tolerance`.
177+
* Add a constraint to the model so that the value of the linear objective of highest priority satisfies a bound given by the values of `abs_tolerance` and/or `rel_tolerance`.
178178
+ If the objective was minimized to a value ``f^*\ge0``, then the constraint ensures that the this objective value is no greater than ``\min(f^*+abs\_tolerance,~f^*\times[1+rel\_tolerance]).``
179179

180180
+ If the objective was minimized to a value ``f^*<0``, then the constraint ensures that the this objective value is no greater than ``\min(f^*+abs\_tolerance,~f^*\times[1-rel\_tolerance]).``
@@ -189,5 +189,3 @@ Note
189189

190190
* Negative values of `abs_tolerance` and `rel_tolerance` will be ignored. This is a convenient way of "switching off" a bounding technique that is not of interest.
191191
* When the model is continuous, no dual information will be returned if there is more than one linear objective.
192-
193-

docs/src/guide/numerics.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Optimization solvers cannot be expected to find the exact solution of
44
a problem, since it may not be possible to represent that solution
5-
using floating-point arithemtic. However, solvers will typically run
5+
using floating-point arithmetic. However, solvers will typically run
66
faster and find more accurate solutions if the problem has good
77
numerical properties. Ideally the optimal value of all primal
88
variables (and dual variables when relevant) will be of order
@@ -52,4 +52,4 @@ is achieved implicitly by scaling their cost and matrix
5252
coefficients. Also, when bounds on variables in a quadratic
5353
programming problem are scaled up (down), the values in the Hessian
5454
matrix must be scaled down (up) so that the overall scaling of the
55-
objective is uniform.
55+
objective is uniform.

docs/src/installation.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ cmake -S. -B build -DHIPO=ON -DCMAKE_TOOLCHAIN_FILE="C:/vcpkg/scripts/buildsyste
8989

9090
##### Path to BLAS
9191

92-
To specify explicitly which BLAS vendor to look for, `BLA_VENDOR` coud be set in CMake, e.g. `-DBLA_VENDOR=Apple` or `-DBLA_VENDOR=OpenBLAS`. Alternatively, to specify which BLAS library to use, set `BLAS_LIBRARIES` to the full path of the library e.g. `-DBLAS_LIBRARIES=/path_to/libopenblas.so`.
92+
To specify explicitly which BLAS vendor to look for, `BLA_VENDOR` could be set in CMake, e.g. `-DBLA_VENDOR=Apple` or `-DBLA_VENDOR=OpenBLAS`. Alternatively, to specify which BLAS library to use, set `BLAS_LIBRARIES` to the full path of the library e.g. `-DBLAS_LIBRARIES=/path_to/libopenblas.so`.
9393

9494

9595
## [Building HiGHS with NVidia GPU support](@id gpu-build)
@@ -129,4 +129,4 @@ It may be necessary to also specify the architecture, e.g.
129129

130130
```
131131
bazel build //... --//:cupdlp_gpu --@rules_cuda//cuda:archs=sm_89
132-
```
132+
```

docs/src/interfaces/python/model-py.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# [Modelling](@id model-py)
22

3-
HiGHS has a rudimentry modelling language that allows models to be built and run using `highspy`.
3+
HiGHS has a rudimentary modelling language that allows models to be built and run using `highspy`.
44

55
Below is an example of building a mathematical LP. The functions used are documented in detail below
66
```
@@ -84,7 +84,3 @@ vals(vars)
8484
```
8585

8686
## MIP Example
87-
88-
89-
90-

docs/src/terminology.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ which case the optimal solution is said to be __non-unique__.
5858

5959
## Primal values
6060

61-
The values of the decision variables are referred to as __primal__ values to distingush them from __dual__ values.
61+
The values of the decision variables are referred to as __primal__ values to distinguish them from __dual__ values.
6262

6363
## Dual values
6464

@@ -160,4 +160,3 @@ relative to the primal bound is a better measure. When the gap reaches
160160
zero then the MIP is solved to optimality. However, it is often
161161
preferable to stop the MIP solver when the relative gap is below a
162162
specified tolerance.
163-

highs/Highs.h

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -266,8 +266,11 @@ class Highs {
266266

267267
/**
268268
* @brief Assess the validity, integrality and feasibility of the
269-
* current primal solution. Of value after calling
270-
* Highs::readSolution
269+
* current primal solution. Row values are computed and checked
270+
* against what's in Highs::solution_.row_value and, if the
271+
* differences exceed a tolerance, valid returns false. If any of
272+
* valid, integral or feasible is false, then assessPrimalSolution
273+
* returns HighsStatus::kWarning.
271274
*/
272275
HighsStatus assessPrimalSolution(bool& valid, bool& integral,
273276
bool& feasible) const;
@@ -1245,7 +1248,15 @@ class Highs {
12451248
HighsStatus setSolution(const HighsSolution& solution);
12461249

12471250
/**
1248-
* @brief Pass a sparse primal solution
1251+
* @brief Pass a primal solution. If index is not a null pointer,
1252+
* then it is assumed that value contains num_entries of packed
1253+
* values, with index defing the corresponding primal solution
1254+
* components. If index is a null pointer, then value is assumed to
1255+
* be a full primal solution.
1256+
*
1257+
* It allows a full primal solution to be passed (for MIPs) without
1258+
* requiring either a HighsSolution that contains (empty vectors of)
1259+
* spurious dual information, or a full list of indices
12491260
*/
12501261
HighsStatus setSolution(const HighsInt num_entries, const HighsInt* index,
12511262
const double* value);

0 commit comments

Comments
 (0)