Skip to content

Commit f10e1e8

Browse files
authored
Support early solver stop without solution in HiGHS (#114)
If you put a stopping option as `.set_option("mip_max_nodes", 10)`, the solver might stop without finding a feasible solution. This modification handles this case by checking that a feasible solution is available, returning `Err(ResolutionError::Other("NoSolutionFound"))` if not. depends on rust-or/highs#32
1 parent b53baa0 commit f10e1e8

File tree

2 files changed

+38
-27
lines changed

2 files changed

+38
-27
lines changed

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "good_lp"
3-
version = "1.14.0"
3+
version = "1.14.1"
44
authors = ["Ophir LOJKINE <[email protected]>"]
55
edition = "2018"
66
repository = "https://github.com/rust-or/good_lp"
@@ -40,7 +40,7 @@ minilp = [
4040
coin_cbc = { version = "0.1", optional = true, default-features = false }
4141
microlp = { version = "0.2.11", optional = true }
4242
lpsolve = { version = "1.0.1", optional = true }
43-
highs = { version = "1.11.0", optional = true }
43+
highs = { version = "2.0.0", optional = true }
4444
russcip = { version = "0.8.2", optional = true }
4545
lp-solvers = { version = "1.0.0", features = ["cplex"], optional = true }
4646
cplex-rs = { version = "0.1", optional = true }

src/solvers/highs.rs

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::{
1010
variable::{UnsolvedProblem, VariableDefinition},
1111
};
1212
use crate::{Constraint, IntoAffineExpression, Variable, WithInitialSolution};
13-
use highs::HighsModelStatus;
13+
use highs::{HighsModelStatus, HighsSolutionStatus};
1414
use std::collections::HashMap;
1515
use std::iter::FromIterator;
1616

@@ -292,32 +292,43 @@ impl SolverModel for HighsProblem {
292292
}
293293

294294
let solved = model.solve();
295-
match solved.status() {
296-
HighsModelStatus::NotSet => Err(ResolutionError::Other("NotSet")),
297-
HighsModelStatus::LoadError => Err(ResolutionError::Other("LoadError")),
298-
HighsModelStatus::ModelError => Err(ResolutionError::Other("ModelError")),
299-
HighsModelStatus::PresolveError => Err(ResolutionError::Other("PresolveError")),
300-
HighsModelStatus::SolveError => Err(ResolutionError::Other("SolveError")),
301-
HighsModelStatus::PostsolveError => Err(ResolutionError::Other("PostsolveError")),
302-
HighsModelStatus::ModelEmpty => Err(ResolutionError::Other("ModelEmpty")),
303-
HighsModelStatus::Infeasible => Err(ResolutionError::Infeasible),
304-
HighsModelStatus::Unbounded => Err(ResolutionError::Unbounded),
305-
HighsModelStatus::UnboundedOrInfeasible => Err(ResolutionError::Infeasible),
306-
HighsModelStatus::ReachedTimeLimit => Ok(HighsSolution {
307-
status: SolutionStatus::TimeLimit,
308-
solution: solved.get_solution(),
309-
}),
310-
_ok_status => {
295+
let status = match solved.status() {
296+
HighsModelStatus::NotSet => return Err(ResolutionError::Other("NotSet")),
297+
HighsModelStatus::LoadError => return Err(ResolutionError::Other("LoadError")),
298+
HighsModelStatus::ModelError => return Err(ResolutionError::Other("ModelError")),
299+
HighsModelStatus::PresolveError => return Err(ResolutionError::Other("PresolveError")),
300+
HighsModelStatus::SolveError => return Err(ResolutionError::Other("SolveError")),
301+
HighsModelStatus::PostsolveError => {
302+
return Err(ResolutionError::Other("PostsolveError"));
303+
}
304+
HighsModelStatus::ModelEmpty => return Err(ResolutionError::Other("ModelEmpty")),
305+
HighsModelStatus::Infeasible => return Err(ResolutionError::Infeasible),
306+
HighsModelStatus::Unbounded => return Err(ResolutionError::Unbounded),
307+
HighsModelStatus::UnboundedOrInfeasible => return Err(ResolutionError::Infeasible),
308+
HighsModelStatus::ReachedTimeLimit
309+
| HighsModelStatus::ReachedSolutionLimit
310+
| HighsModelStatus::ReachedInterrupt
311+
| HighsModelStatus::ReachedIterationLimit
312+
| HighsModelStatus::ReachedMemoryLimit => SolutionStatus::TimeLimit,
313+
HighsModelStatus::Optimal
314+
| HighsModelStatus::ObjectiveBound
315+
| HighsModelStatus::ObjectiveTarget => {
311316
let gap = solved.mip_gap();
312-
Ok(HighsSolution {
313-
status: if gap.is_finite() && gap > 0.0 {
314-
SolutionStatus::GapLimit
315-
} else {
316-
SolutionStatus::Optimal
317-
},
318-
solution: solved.get_solution(),
319-
})
317+
if gap.is_finite() && gap > 0.0 {
318+
SolutionStatus::GapLimit
319+
} else {
320+
SolutionStatus::Optimal
321+
}
320322
}
323+
_ => return Err(ResolutionError::Other("Unknown")),
324+
};
325+
if solved.primal_solution_status() == HighsSolutionStatus::Feasible {
326+
Ok(HighsSolution {
327+
status,
328+
solution: solved.get_solution(),
329+
})
330+
} else {
331+
Err(ResolutionError::Other("NoSolutionFound"))
321332
}
322333
}
323334

0 commit comments

Comments
 (0)