Open TendTo opened 4 months ago
Thanks for posting this well-defined issue. I will look into it. I'm guessing this is on the current master?
Just to note: Warm-starting (while still beneficial) is not as impactful in rational solving as in normal double-precision mode.
Yeah this is definitely a bug in SoPlex (not even the rational part, the same happens if you solve with tolerances). It sets the row to be nonbasic-free after you deactivate it, but the implementation does not consider these types for entering the basis apparently. I will try to fix this, but it might take some time. As a workaround in the meantime, you could set the rhs to some big M (effectively also deactivating it), and it will work correctly.
Thanks, I'll quickly update you:
If I find any other insights, I'll share them here.
void check_and_print_solution_real(SoPlex &soplex, double expected) {
SPxSolver::Status stat;
VectorReal prim(2);
VectorReal dual(1);
stat = soplex.optimize();
if (stat != SPxSolver::OPTIMAL || soplex.objValueRational() != expected) {
throw std::runtime_error((std::stringstream{} << "SoPlex returned with status " << std::to_string(stat)
<< " and objective value " << soplex.objValueRational()
<< " instead of " << expected)
.str());
}
soplex.getPrimal(prim);
soplex.getDual(dual);
std::cout << "LP solved to optimality.\n";
std::cout << "Objective value is " << soplex.objValueRational() << ".\n";
std::cout << "Primal solution is [" << prim[0] << ", " << prim[1] << "].\n";
std::cout << "Dual solution is [" << dual[0] << "].\n";
}
void test_real() {
SoPlex mysoplex;
/* set the objective sense (MAXIMIZE) */
mysoplex.setIntParam(SoPlex::OBJSENSE, SoPlex::OBJSENSE_MAXIMIZE);
/* we first add variables (the integer data is converted to type Rational) */
mysoplex.addColReal(LPColReal(0, DSVectorReal(), infinity, 0)); // 0 <= x0 | 0 coeff
mysoplex.addColReal(LPColReal(1, DSVectorReal(), 1, 0)); // 0 <= x1 <= 1 | 1 coeff
/* then constraints one by one (here we show how Rationals can be used
* directly) */
DSVectorReal row1(2);
row1.add(0, 1);
row1.add(1, 1);
mysoplex.addRowReal(LPRowReal(-infinity, row1, infinity)); // C0: 1 + 1 <= infinity
// Maximize
// obj: 1 x1
// Subject To
// C0 : 1 x0 + 1 x1 <= infinity
// Bounds
// x1 <= 1
// End
check_and_print_solution_real(mysoplex, 1);
// Maximize
// obj: 1 x1
// Subject To
// C0 : 1 x0 + 1 x1 <= 0
// Bounds
// x1 <= 1
// End
// Enable C0
mysoplex.changeRangeReal(0, -infinity, 0); // C0: 1 + 1 <= 0
check_and_print_solution_real(mysoplex, 0);
// Maximize
// obj: 1 x1
// Subject To
// C0 : 1 x0 + 1 x1 <= infinity
// Bounds
// x1 <= 1
// End
// Disable C0 again
mysoplex.changeRangeReal(0, -infinity, infinity); // C1: 1 + 0 <= infinity
// ATTEMPTS
// Adding this fixes the issue, but it's very expensive, especially for repeated solves
// mysoplex.clearBasis();
// Gets ignored because of SYNCMODE_AUTO
// mysoplex.syncLPReal();
// Does nothing
// mysoplex._syncLPReal();
// Does nothing
// mysoplex._solver.reLoad();
// Solve the issue, but requires private access and still expensive
// mysoplex._hasBasis = false;
// ERROR: we don't get 1 anymore (like in the original problem), but 0 (like the last one)
check_and_print_solution_real(mysoplex, 1);
}
You could try the following workaround (not even a bad one, maybe): Set the rhs to the max. activity of the constraint + small positive value (0.1, e.g.). By max. activity I mean any upper bound on a^T x over the feasible region (assuming the constraint reads a^T x <= b). This effectively removes the constraint, should avoid SoPlex's problems with infinity, and give you a hotstart. It is trivially possible if all your variables have finite bounds.
In my use case, I need to solve the same LP problem many times, enabling and disabling the same constraints as required. I noticed that more often than not the results after a previous solve were incorrect, as if they were stuck in the previous basis.
In fact, forcing a refresh with
clearBasis
between each solve fixes the issue, but that means having a cold start every time. I was wondering if this behaviour is intended or something is going wrong within the internals of the solver.The following is minimal snippet to reproduce the problem. I have just started investigating it, so I probably missed something.