Skip to content

Commit 451a0ed

Browse files
committed
fix division by zero error in linear presolve
1 parent 543b4b9 commit 451a0ed

File tree

3 files changed

+82
-16
lines changed

3 files changed

+82
-16
lines changed

pyomo/contrib/solver/ipopt.py

+32-15
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@
1717

1818
from pyomo.common import Executable
1919
from pyomo.common.config import ConfigValue, document_kwargs_from_configdict, ConfigDict
20-
from pyomo.common.errors import PyomoException, DeveloperError
20+
from pyomo.common.errors import (
21+
PyomoException,
22+
DeveloperError,
23+
InfeasibleConstraintException,
24+
)
2125
from pyomo.common.tempfiles import TempfileManager
2226
from pyomo.common.timing import HierarchicalTimer
2327
from pyomo.core.base.var import _GeneralVarData
@@ -314,15 +318,19 @@ def solve(self, model, **kwds):
314318
) as row_file, open(basename + '.col', 'w') as col_file:
315319
timer.start('write_nl_file')
316320
self._writer.config.set_value(config.writer_config)
317-
nl_info = self._writer.write(
318-
model,
319-
nl_file,
320-
row_file,
321-
col_file,
322-
symbolic_solver_labels=config.symbolic_solver_labels,
323-
)
321+
try:
322+
nl_info = self._writer.write(
323+
model,
324+
nl_file,
325+
row_file,
326+
col_file,
327+
symbolic_solver_labels=config.symbolic_solver_labels,
328+
)
329+
proven_infeasible = False
330+
except InfeasibleConstraintException:
331+
proven_infeasible = True
324332
timer.stop('write_nl_file')
325-
if len(nl_info.variables) > 0:
333+
if not proven_infeasible and len(nl_info.variables) > 0:
326334
# Get a copy of the environment to pass to the subprocess
327335
env = os.environ.copy()
328336
if nl_info.external_function_libraries:
@@ -361,11 +369,20 @@ def solve(self, model, **kwds):
361369
timer.stop('subprocess')
362370
# This is the stuff we need to parse to get the iterations
363371
# and time
364-
iters, ipopt_time_nofunc, ipopt_time_func, ipopt_total_time = (
365-
self._parse_ipopt_output(ostreams[0])
366-
)
367-
368-
if len(nl_info.variables) == 0:
372+
(
373+
iters,
374+
ipopt_time_nofunc,
375+
ipopt_time_func,
376+
ipopt_total_time,
377+
) = self._parse_ipopt_output(ostreams[0])
378+
379+
if proven_infeasible:
380+
results = Results()
381+
results.termination_condition = TerminationCondition.provenInfeasible
382+
results.solution_loader = SolSolutionLoader(None, None)
383+
results.iteration_count = 0
384+
results.timing_info.total_seconds = 0
385+
elif len(nl_info.variables) == 0:
369386
if len(nl_info.eliminated_vars) == 0:
370387
results = Results()
371388
results.termination_condition = TerminationCondition.emptyModel
@@ -457,7 +474,7 @@ def solve(self, model, **kwds):
457474
)
458475

459476
results.solver_configuration = config
460-
if len(nl_info.variables) > 0:
477+
if not proven_infeasible and len(nl_info.variables) > 0:
461478
results.solver_log = ostreams[0].getvalue()
462479

463480
# Capture/record end-time / wall-time

pyomo/contrib/solver/tests/solvers/test_solvers.py

+34
Original file line numberDiff line numberDiff line change
@@ -1535,6 +1535,40 @@ def test_presolve_with_zero_coef(
15351535
self.assertAlmostEqual(m.y.value, 0)
15361536
self.assertAlmostEqual(m.z.value, 0)
15371537

1538+
m.x.setlb(2)
1539+
res = opt.solve(
1540+
m, load_solutions=False, raise_exception_on_nonoptimal_result=False
1541+
)
1542+
self.assertEqual(
1543+
res.termination_condition, TerminationCondition.provenInfeasible
1544+
)
1545+
1546+
m = pe.ConcreteModel()
1547+
m.w = pe.Var()
1548+
m.x = pe.Var()
1549+
m.y = pe.Var()
1550+
m.z = pe.Var()
1551+
m.obj = pe.Objective(expr=m.x**2 + m.y**2 + m.z**2 + m.w**2)
1552+
m.c1 = pe.Constraint(expr=m.x + m.w == m.y + m.z)
1553+
m.c2 = pe.Constraint(expr=m.z == -m.y)
1554+
m.c3 = pe.Constraint(expr=m.x == -m.w)
1555+
1556+
res = opt.solve(m)
1557+
self.assertAlmostEqual(res.incumbent_objective, 0)
1558+
self.assertAlmostEqual(m.w.value, 0)
1559+
self.assertAlmostEqual(m.x.value, 0)
1560+
self.assertAlmostEqual(m.y.value, 0)
1561+
self.assertAlmostEqual(m.z.value, 0)
1562+
1563+
del m.c1
1564+
m.c1 = pe.Constraint(expr=m.x + m.w == m.y + m.z + 1.5)
1565+
res = opt.solve(
1566+
m, load_solutions=False, raise_exception_on_nonoptimal_result=False
1567+
)
1568+
self.assertEqual(
1569+
res.termination_condition, TerminationCondition.provenInfeasible
1570+
)
1571+
15381572
@parameterized.expand(input=_load_tests(all_solvers))
15391573
def test_scaling(self, name: str, opt_class: Type[SolverBase], use_presolve: bool):
15401574
opt: SolverBase = opt_class()

pyomo/repn/plugins/nl_writer.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -1781,6 +1781,19 @@ def _linear_presolve(self, comp_by_linear_var, lcon_by_linear_nnz, var_bounds):
17811781
):
17821782
_id, id2 = id2, _id
17831783
coef, coef2 = coef2, coef
1784+
if coef == 0 and coef2 == 0:
1785+
if abs(expr_info.const) <= TOL:
1786+
# constraint is trivially satisfied
1787+
# we cannot use this constraint to
1788+
# eliminate either variable
1789+
eliminated_cons.add(con_id)
1790+
continue
1791+
else:
1792+
# constraint is trivially infeasible
1793+
raise InfeasibleConstraintException(
1794+
f"Model is infeasible: {expr_info.const} == "
1795+
+ f"{coef}*{var_map[_id]} + {coef2}*{var_map[id2]}"
1796+
)
17841797
# substituting _id with a*x + b
17851798
a = -coef2 / coef
17861799
x = id2
@@ -1799,7 +1812,9 @@ def _linear_presolve(self, comp_by_linear_var, lcon_by_linear_nnz, var_bounds):
17991812
# Tighten variable bounds
18001813
lb, ub = var_bounds[_id]
18011814
if a == 0:
1802-
if (lb is not None and lb > b) or (ub is not None and ub < b):
1815+
if (lb is not None and lb > b + TOL) or (
1816+
ub is not None and ub < b - TOL
1817+
):
18031818
raise InfeasibleConstraintException(
18041819
f'Model is infeasible: {lb} <= {var_map[_id]} <= {ub} '
18051820
+ f'and {var_map[_id]} == {a}*{var_map[x]} + {b} cannot '

0 commit comments

Comments
 (0)