-
Notifications
You must be signed in to change notification settings - Fork 560
Description
Summary
In contrib.solver.solvers.highs.py, the _postsolve method records the simplex iteration count:
results.iteration_count = info.simplex_iteration_count
However, the highs solver might use the simplex, ipm, pdlp, mip, or qp solvers depending on the problem. Each method has its own iteration count. The unused methods are populated with either -1 or 0 (I can't find rhyme or reason to which and when). Also, if the problem is infeasible, the entire highs info might be invalid.
Steps to reproduce the issue
The latter case can be demonstrated with
from pyomo.environ import (
ConcreteModel, Var, Reals, Objective, SolverFactory,
)
def main():
m = ConcreteModel()
m.x1 = Var(within=Reals, bounds=(0, 2), initialize=1.745)
m.x4 = Var(within=Reals, bounds=(0, 5), initialize=3.048)
m.x7 = Var(within=Reals, bounds=(0.9, 0.95), initialize=0.928)
m.obj = Objective(expr=-6.3 * m.x4 * m.x7 + 5.04 * m.x1)
opt = SolverFactory("highs")
opt.solve(m, tee=False)
if __name__ == "__main__":
main()
In general, we can check the iteration values for all types of problems:
from pyomo.environ import (
ConcreteModel, Var, Objective, Constraint,
NonNegativeReals, Binary, Reals, minimize, maximize,
SolverFactory, value
)
def solve_lp(method: str):
"""Tiny LP, solved with a specific HiGHS LP method (simplex/ipm/pdlp)."""
m = ConcreteModel()
m.x = Var(domain=NonNegativeReals)
m.y = Var(domain=NonNegativeReals)
# min x + y s.t. x + y >= 1
m.c = Constraint(expr=m.x + m.y >= 1)
m.obj = Objective(expr=m.x + m.y, sense=minimize)
opt = SolverFactory("highs")
opt.options["solver"] = method # 'simplex' | 'ipm' | 'pdlp'
res = opt.solve(m, tee=False)
print(f"[LP/{method}] status={res.solver.status}, term={res.solver.termination_condition}, obj={value(m.obj):.6f}")
print(f" x={value(m.x):.6f}, y={value(m.y):.6f}")
def solve_mip():
"""Tiny MIP (binary knapsack)."""
m = ConcreteModel()
m.a = Var(domain=Binary)
m.b = Var(domain=Binary)
# max 3a + 2b s.t. a + b <= 1
m.cap = Constraint(expr=m.a + m.b <= 1)
m.obj = Objective(expr=3*m.a + 2*m.b, sense=maximize)
opt = SolverFactory("highs") # let HiGHS choose the MIP machinery
res = opt.solve(m, tee=False)
print(f"[MIP] status={res.solver.status}, term={res.solver.termination_condition}, obj={value(m.obj):.6f}")
print(f" a={int(round(value(m.a)))}, b={int(round(value(m.b)))}")
def solve_qp():
"""Tiny convex QP."""
m = ConcreteModel()
m.x = Var(domain=Reals, bounds=(0, None))
m.y = Var(domain=Reals, bounds=(0, None))
# min (x-1)^2 + (y-2)^2 s.t. x + y >= 1
m.c = Constraint(expr=m.x + m.y >= 1)
m.obj = Objective(expr=(m.x - 1)**2 + (m.y - 2)**2, sense=minimize)
opt = SolverFactory("highs") # HiGHS handles convex QP
res = opt.solve(m, tee=False)
print(f"[QP] status={res.solver.status}, term={res.solver.termination_condition}, obj={value(m.obj):.6f}")
print(f" x={value(m.x):.6f}, y={value(m.y):.6f}")
def main():
print("=== LP methods ===")
for method in ("simplex", "ipm", "pdlp"):
solve_lp(method)
print("\n=== MIP ===")
solve_mip()
print("\n=== QP (convex) ===")
solve_qp()
if __name__ == "__main__":
main()
Error Message
For the former example, we get
ValueError: invalid value for configuration 'iteration_count':
Failed casting -1
to NonNegativeInt
Error: Expected non-negative int, but received -1
because info.simplex_iteration_count is -1.
Information on your system
Pyomo version: 6.9.4
Python version: 3.12.3
Operating system: windows 11
How Pyomo was installed (PyPI, conda, source): source
Solver (if applicable): highs
Additional information
I suggest the following fix to replace line 753 of contrib/solver/solvers/highs.py:
if info.valid:
# The method that ran will have a non-negative iteration count
# and the others will be 0 or -1.
max_iters = max(
info.simplex_iteration_count,
info.ipm_iteration_count,
info.mip_node_count,
info.pdlp_iteration_count,
info.qp_iteration_count,
)
results.iteration_count = max_iters if max_iters != -1 else 0
else:
results.iteration_count = 0
If this is appropriate I can make a PR.