Description
Summary
Under certain circumstances, it can happen that SCIP reports to have found an optimal solution, and afterward reports a constraint violation that declares the solution infeasible for the original problem. In the way that the SCIP log file is read by Pyomo at the moment (cf. https://github.com/henriquejsfj/pyomo/blob/3ce74167beac24f4ffd963fef8d0f57a0979ea69/pyomo/solvers/plugins/solvers/SCIPAMPL.py#L429), this message will not be detected so that the solution would still be considered optimal by Pyomo even tough (in my understanding) it should not.
Steps to reproduce the issue
The issue tracker of PySCIPOpt shows examples of the solver log output in such situations, cf. scipopt/PySCIPOpt#720, scipopt/PySCIPOpt#242. After the "Gap" information line, SCIP adds multiple lines of information about the constraint violation and finally that the best solution is not feasible in the original problem.
Given the information in scipopt/PySCIPOpt#242 I was able to reproduce the behavior with the following simple example:
import pyomo.environ as pyo
m = pyo.ConcreteModel()
m.b = pyo.Var([0], domain=pyo.Binary)
m.o = pyo.Objective(expr=m.b[0], sense=pyo.maximize)
m.c = pyo.ConstraintList()
m.c.add(m.o <= m.b[0] / (1.1 * m.b[0]))
solver = pyo.SolverFactory("scip")
results = solver.solve(m, tee=True)
print("\nTermination condition:", results.solver.termination_condition, "\n")
m.b.pprint()
The above code returns (I have shortened the output to the parts that I consider relevant):
[...]
solve problem
=============
Changing lower bound for child of pow(.,-1) from -0 to 1e-09.
Check your model formulation or use option expr/pow/minzerodistance to avoid this warning.
Expression: ((1.1*<t_i0>))^(-1)
presolving:
(round 1, fast) 0 del vars, 1 del conss, 0 add conss, 1 chg bounds, 0 chg sides, 0 chg coeffs, 0 upgd conss, 0 impls, 0 clqs
presolving (2 rounds: 2 fast, 1 medium, 1 exhaustive):
1 deleted vars, 1 deleted constraints, 0 added constraints, 1 tightened bounds, 0 added holes, 0 changed sides, 0 changed coefficients
0 implications, 0 cliques
transformed 1/1 original solutions to the transformed problem space
Presolving Time: 0.00
SCIP Status : problem is solved [optimal solution found]
Solving Time (sec) : 0.00
Solving Nodes : 0
Primal Bound : +0.00000000000000e+00 (1 solutions)
Dual Bound : +0.00000000000000e+00
Gap : 0.00 %
[nonlinear] <nlc0>: -<i0>*((1.1*<i0>))^(-1)+<i0> <= 0;
violation: left hand side is violated by 1e+20
violation: right hand side is violated by 1e+20
best solution is not feasible in original problem
Termination condition: optimal
b : Size=1, Index={0}
Key : Lower : Value : Upper : Fixed : Stale : Domain
0 : 0 : -0.0 : 1 : False : False : Binary
This shows that if a user would not read the solver logs, they would consider the solution as optimal due to the Pyomo solver termination condition, even though the solution b[0] = 0
leads to a division by zero.
Information on your system
Pyomo version: 6.9.1
Python version: 3.11.2
Operating system: Debian 12
How Pyomo was installed (PyPI, conda, source): PyPI
Solver (if applicable): SCIP 9.2.2
Additional information
I think the problem could be adressed by looking for the line best solution is not feasible in original problem
in the solver log and re-setting the termination condition to another status (I think unknown
might be appropriate?) if the line is present.
If it is helpful for you, I could try to prepare a corresponding pull request.