Skip to content

Commit 498eae1

Browse files
authored
Merge pull request #2691 from ERGO-Code/fix-2653-local
Fix spurious primal infeasibilities
2 parents 6cc4115 + ea19846 commit 498eae1

File tree

3 files changed

+67
-17
lines changed

3 files changed

+67
-17
lines changed

highs/lp_data/HighsSolution.cpp

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -549,28 +549,24 @@ void getVariableKktFailures(const double primal_feasibility_tolerance,
549549
const HighsVarType integrality,
550550
double& primal_infeasibility,
551551
double& dual_infeasibility, uint8_t& at_status,
552-
uint8_t& mid_status) {
553-
const HighsInt feasibility_tolerance_mu = 0.0;
554-
// @primal_infeasibility calculation
555-
primal_infeasibility = 0;
556-
if (value < lower - primal_feasibility_tolerance * feasibility_tolerance_mu) {
557-
// Below lower
558-
primal_infeasibility = lower - value;
559-
} else if (value >
560-
upper + primal_feasibility_tolerance * feasibility_tolerance_mu) {
561-
// Above upper
562-
primal_infeasibility = value - upper;
563-
}
552+
uint8_t& mid_status, const HighsInt index) {
553+
// Return the primal residual (ie infeasibility with zero tolerance)
554+
// as the primal infeasibility, ensuring (cf #2653) that it doesn't
555+
// exceed the primal feasibility tolerance if the standard primal
556+
// infeasibility (ie infeasibility exceeding the tolerance) is zero
557+
auto infeasibility_residual =
558+
infeasibility(lower, value, upper, primal_feasibility_tolerance);
559+
primal_infeasibility = infeasibility_residual.second;
564560
// Determine whether this value is close to a bound
565561
at_status = kHighsSolutionNo;
566-
double residual = std::fabs(lower - value);
567-
if (residual * residual <= primal_feasibility_tolerance) {
562+
double bound_residual = std::fabs(lower - value);
563+
if (bound_residual * bound_residual <= primal_feasibility_tolerance) {
568564
// Close to lower bound
569565
at_status = kHighsSolutionLo;
570566
} else {
571567
// Not close to lower bound: maybe close to upper bound
572-
residual = std::fabs(value - upper);
573-
if (residual * residual <= primal_feasibility_tolerance)
568+
bound_residual = std::fabs(value - upper);
569+
if (bound_residual * bound_residual <= primal_feasibility_tolerance)
574570
at_status = kHighsSolutionUp;
575571
}
576572
// Look for dual sign errors that exceed the tolerance. For boxed

highs/lp_data/HighsSolution.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ void getVariableKktFailures(const double primal_feasibility_tolerance,
9090
const HighsVarType integrality,
9191
double& primal_infeasibility,
9292
double& dual_infeasibility, uint8_t& at_status,
93-
uint8_t& mid_status);
93+
uint8_t& mid_status, const HighsInt index = 0);
9494

9595
void getPrimalDualGlpsolErrors(const HighsOptions& options, const HighsLp& lp,
9696
const std::vector<double>& gradient,

highs/util/HighsUtils.h

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,4 +214,58 @@ inline T fractionality(T input, T* intval = nullptr) {
214214
if (intval != nullptr) *intval = val;
215215
return abs(input - val);
216216
}
217+
218+
inline std::pair<double, double> infeasibility(const double lower,
219+
const double value,
220+
const double upper,
221+
const double tolerance) {
222+
using std::fabs;
223+
using std::min;
224+
double residual = 0;
225+
double infeasibility = 0;
226+
// Determine the infeasibility exceeding the tolerance used in
227+
// computing the number of infeasibilities in a basic solution -
228+
// which defines its feasibility
229+
//
230+
// @primal_infeasibility calculation
231+
if (value < lower - tolerance) infeasibility = lower - value;
232+
if (value > upper + tolerance) infeasibility = value - upper;
233+
// Determine the residual used in computing the sum of
234+
// infeasibilities and max infeasibility - which are just for
235+
// reporting
236+
if (tolerance > 0) {
237+
if (value < lower) residual = lower - value;
238+
if (value > upper) residual = value - upper;
239+
} else {
240+
residual = infeasibility;
241+
}
242+
// Now, if the bound defining the residual is large, it's possible
243+
// for the infeasibility to be zero, but the residual to exceed the
244+
// tolerance due to numerical rounding
245+
//
246+
// Case in point is #2653 where row 1 has l = 157345 and the
247+
// activity gives zero infeasibility, but a residual of
248+
// 1.00000761449e-06, exceeding the tolerance of 1e-6 by delta =
249+
// 7.61449e-12.
250+
//
251+
// Now delta / l = 4.83937e-17, which is less than machine precision
252+
// (2.22045e-16), so the reliable residual value should ignore this
253+
// delta.
254+
//
255+
// In general, it might be possible to subtract delta from the
256+
// residual conditional on (something like)
257+
//
258+
// delta < 1e1 * max(1.0, fabs(bound_value)) * kHighsMacheps
259+
//
260+
// to give a reasonable value but, in practice, when infeasibility is
261+
// 0, it would seem fine to set
262+
//
263+
// residual = min(residual, tolerance)
264+
//
265+
// so that values of maximum infeasibility defined by residual
266+
// doesn't exceed the tolerance
267+
//
268+
if (infeasibility == 0) residual = min(residual, tolerance);
269+
return std::make_pair(infeasibility, residual);
270+
}
217271
#endif // UTIL_HIGHSUTILS_H_

0 commit comments

Comments
 (0)