LP writer dies given a Var with an empty domain
Summary
If I create a Var that has an empty domain created by intersecting Binary and $\{2, 3\}$ (the model is structurally infeasible), the LP writer croaks somewhere in figuring out the domain (I think).
Steps to reproduce the issue
from pyomo.environ import *
m = ConcreteModel()
m.thing = Set(initialize=[2, 3])
m.x = Var(domain=Binary & m.thing)
m.c = Constraint(expr=m.x >= 0.5)
m.obj = Objective(expr=m.x)
SolverFactory('gurobi').solve(m, tee=True)
Error Message
Traceback (most recent call last):
File "/home/esjohn/src/pyomo/pyomo/core/base/set.py", line 729, in _get_discrete_interval
step = min(abs(r.step) for r in ranges if r.step != 0)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ValueError: min() arg is an empty sequence
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/esjohn/src/pyomo/just_checking.py", line 11, in <module>
SolverFactory('gurobi').solve(m, tee=True)
File "/home/esjohn/src/pyomo/pyomo/opt/base/solvers.py", line 598, in solve
self._presolve(*args, **kwds)
File "/home/esjohn/src/pyomo/pyomo/solvers/plugins/solvers/GUROBI.py", line 249, in _presolve
ILMLicensedSystemCallSolver._presolve(self, *args, **kwds)
File "/home/esjohn/src/pyomo/pyomo/opt/solver/shellcmd.py", line 223, in _presolve
OptSolver._presolve(self, *args, **kwds)
File "/home/esjohn/src/pyomo/pyomo/opt/base/solvers.py", line 704, in _presolve
self._convert_problem(
File "/home/esjohn/src/pyomo/pyomo/opt/base/solvers.py", line 756, in _convert_problem
return convert_problem(
^^^^^^^^^^^^^^^^
File "/home/esjohn/src/pyomo/pyomo/opt/base/convert.py", line 97, in convert_problem
problem_files, symbol_map = converter.apply(*tmp, **tmpkw)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/esjohn/src/pyomo/pyomo/solvers/plugins/converter/model.py", line 78, in apply
(problem_filename, symbol_map_id) = instance.write(
^^^^^^^^^^^^^^^
File "/home/esjohn/src/pyomo/pyomo/core/base/block.py", line 1940, in write
(filename, smap) = problem_writer(self, filename, solver_capability, io_options)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/esjohn/src/pyomo/pyomo/repn/plugins/lp_writer.py", line 208, in __call__
info = self.write(model, FILE, **io_options)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/esjohn/src/pyomo/pyomo/repn/plugins/lp_writer.py", line 241, in write
return _LPWriter_impl(ostream, config).write(model)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/esjohn/src/pyomo/pyomo/repn/plugins/lp_writer.py", line 522, in write
if v.is_binary():
^^^^^^^^^^^^^
File "/home/esjohn/src/pyomo/pyomo/core/base/var.py", line 190, in is_binary
return domain.get_interval() == (0, 1, 1)
^^^^^^^^^^^^^^^^^^^^^
File "/home/esjohn/src/pyomo/pyomo/core/base/set.py", line 704, in get_interval
return self._get_discrete_interval()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/esjohn/src/pyomo/pyomo/core/base/set.py", line 738, in _get_discrete_interval
return (vals[0], vals[0], 0)
~~~~^^^
IndexError: list index out of range
Information on your system
Pyomo version: main Python version: 3.11 Operating system: How Pyomo was installed (PyPI, conda, source): Solver (if applicable):
Hi, I would like to understand the correct behavior we would want to see here to be able to provide a fix for this. I have tried replacing Binary & m.thing by Set(initialize=[]) and this runs fine with the solver 'gurobi' and yield the lp below. Would you expect this model in your example ?
* Source Pyomo model name=unknown *\
min x1: +1 x2
s.t.
c_l_x3_: +1 x2 >= 0.5
bounds -inf <= x2 <= +inf end
I think the correct behavior is for get_interval() to return (None, None, None) for Sets with no ranges.
That should cause the LP writer (and all the other writers) to die on error: the variable is not binary, integer, or continuous, so the model is not a (MI)LP [or (MI)QP] -- and not compatible with the writer format (so the fact that the LP writer is OK with your modified model is also probably an error). My feeling is that domain errors like this aren't quite the same thing as an "infeasible model", and should probably return a different error (I could also probably be convinced that I am wrong - so it would be good to bring this up at the Dev call or have others weigh in here).
BWOM: We looked at this issue during the dev call on 1/21/2025 and @jsiirola volunteered to look at it.