-
Notifications
You must be signed in to change notification settings - Fork 568
Description
Summary
I ran gdpopt.ldsda using mindtpy as the MINLP subproblem solver. The algorithm returned a results object that contains no solutions.
This appears related to MindtPy’s short-circuit (e.g., all discrete variables are fixed or just an NLP problem), where MindtPy may directly solve an LP/NLP but does not reliably return a populated SolverResults / does not load primal values back onto the model instance provided to solve().
Steps to reproduce the issue
Reproducer A (direct MindtPy; “no discrete decisions” path)
Run the following script:
# example.py
from pyomo.environ import ConcreteModel, Var, Objective, Constraint, Binary, NonNegativeReals, value, SolverFactory
m = ConcreteModel()
m.x = Var(domain=NonNegativeReals)
m.y = Var(domain=Binary)
# Fix the discrete variable so MindtPy detects "no discrete decisions"
m.y.fix(0)
# NLP constraint so MindtPy takes the NLP direct-solve branch
m.c = Constraint(expr=m.x**2 >= 1 + m.y)
m.obj = Objective(expr=m.x)
res = SolverFactory("mindtpy").solve(
m,
strategy="OA",
nlp_solver="ipopt",
# mip_solver can be provided but should not matter if there are no discretes
mip_solver="appsi_highs",
)
print("res is None:", res is None)Observed in the test output:
res is None: True
Expected behavior
res is None: False
Reproducer B (GDPopt LD-SDA gams free test)
Please change the pyomo/contrib/gdpopt/test/test_ldsda.py to:
class TestGDPoptLDSDA(unittest.TestCase):
....notations...
@unittest.skipUnless(
all(
(
SolverFactory("mindtpy").available(False),
SolverFactory("appsi_highs").available(False),
SolverFactory("ipopt").available(False),
)
),
"mindtpy/appsi_highs/ipopt not available",
)
def test_solve_four_stage_dynamic_model_minimize(self):
model = build_model(mode_transfer=True)
discretizer = TransformationFactory("dae.collocation")
discretizer.apply_to(model, nfe=10, ncp=3, scheme="LAGRANGE-RADAU")
for disjunct in model.component_data_objects(ctype=Disjunct):
for constraint in disjunct.component_objects(ctype=Constraint):
constraint._constructed = False
constraint.construct()
for dxdt in model.component_data_objects(ctype=Var, descend_into=True):
if "dxdt" in dxdt.name:
dxdt.setlb(-300)
dxdt.setub(300)
for direction_norm in ["L2", "Linf"]:
results = SolverFactory("gdpopt.ldsda").solve(
model,
direction_norm=direction_norm,
minlp_solver="mindtpy",
minlp_solver_args={"mip_solver": "appsi_highs", "nlp_solver": "ipopt"},
# minlp_solver="gams",
# minlp_solver_args={'solver': "ipopt"},
starting_point=[1, 2],
logical_constraint_list=[
model.mode_transfer_lc1,
model.mode_transfer_lc2,
],
time_limit=100,
)
self.assertAlmostEqual(value(model.obj), -23.305325, places=4)
...the rest of test unchanged...pytest <path_to_test.py>Error Message
The error message
============================================= test session starts =============================================
platform win32 -- Python 3.12.12, pytest-8.4.2, pluggy-1.5.0
rootdir:
configfile: pyproject.toml
plugins: cov-7.0.0
collected 10 items
pyomo\contrib\gdpopt\tests\test_ldsda.py F......... [100%]
================================================== FAILURES ===================================================
________________________ TestGDPoptLDSDA.test_solve_four_stage_dynamic_model_minimize _________________________
self = <pyomo.contrib.gdpopt.tests.test_ldsda.TestGDPoptLDSDA testMethod=test_solve_four_stage_dynamic_model_minimize>
@unittest.skipUnless(
all(
(
SolverFactory("mindtpy").available(False),
SolverFactory("appsi_highs").available(False),
SolverFactory("ipopt").available(False),
)
),
"mindtpy/appsi_highs/ipopt not available",
)
def test_solve_four_stage_dynamic_model_minimize(self):
model = build_model(mode_transfer=True)
discretizer = TransformationFactory("dae.collocation")
discretizer.apply_to(model, nfe=10, ncp=3, scheme="LAGRANGE-RADAU")
for disjunct in model.component_data_objects(ctype=Disjunct):
for constraint in disjunct.component_objects(ctype=Constraint):
constraint._constructed = False
constraint.construct()
for dxdt in model.component_data_objects(ctype=Var, descend_into=True):
if "dxdt" in dxdt.name:
dxdt.setlb(-300)
dxdt.setub(300)
for direction_norm in ["L2", "Linf"]:
results = SolverFactory("gdpopt.ldsda").solve(
model,
direction_norm=direction_norm,
minlp_solver="mindtpy",
minlp_solver_args={"mip_solver": "appsi_highs", "nlp_solver": "ipopt"},
# minlp_solver="gams",
# minlp_solver_args={'solver': "ipopt"},
starting_point=[1, 2],
logical_constraint_list=[
model.mode_transfer_lc1,
model.mode_transfer_lc2,
],
time_limit=100,
)
> self.assertAlmostEqual(value(model.obj), -23.305325, places=4)
^^^^^^^^^^^^^^^^
pyomo\contrib\gdpopt\tests\test_ldsda.py:75:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
pyomo\common\numeric_types.py:378: in value
tmp = obj(exception=True)
^^^^^^^^^^^^^^^^^^^
pyomo\core\base\objective.py:462: in __call__
return super().__call__(exception)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
pyomo\core\base\expression.py:63: in __call__
return arg(exception=exception)
^^^^^^^^^^^^^^^^^^^^^^^^
pyomo\core\expr\base.py:116: in __call__
return visitor.evaluate_expression(self, exception)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pyomo\core\expr\visitor.py:1312: in evaluate_expression
ans = visitor.dfs_postorder_stack(exp)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pyomo\core\expr\visitor.py:930: in dfs_postorder_stack
flag, value = self.visiting_potential_leaf(_sub)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pyomo\core\expr\visitor.py:1213: in visiting_potential_leaf
return True, value(node, exception=self.exception)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
obj = <pyomo.core.base.var.VarData object at 0x000002E32D106F90>, exception = True
def value(obj, exception=True):
"""
A utility function that returns the value of a Pyomo object or
expression.
Args:
obj: The argument to evaluate. If it is None, a
string, or any other primitive numeric type,
then this function simply returns the argument.
Otherwise, if the argument is a NumericValue
then the __call__ method is executed.
exception (bool): If :const:`True`, then an exception should
be raised when instances of NumericValue fail to
evaluate due to one or more objects not being
initialized to a numeric value (e.g, one or more
variables in an algebraic expression having the
value None). If :const:`False`, then the function
returns :const:`None` when an exception occurs.
Default is True.
Returns: A numeric value or None.
"""
if obj.__class__ in native_types:
return obj
#
# Test if we have a duck typed Pyomo expression
#
if not hasattr(obj, 'is_numeric_type'):
#
# TODO: Historically we checked for new *numeric* types and
# raised exceptions for anything else. That is inconsistent
# with allowing native_types like None/str/bool to be returned
# from value(). We should revisit if that is worthwhile to do
# here.
#
if check_if_numeric_type(obj):
return obj
else:
if not exception:
return None
raise TypeError(
"Cannot evaluate object with unknown type: %s" % obj.__class__.__name__
)
#
# Evaluate the expression object
#
if exception:
#
# Here, we try to catch the exception
#
try:
tmp = obj(exception=True)
if tmp is None:
> raise ValueError(
"No value for uninitialized %s object %s"
% (type(obj).__name__, obj.name)
)
E ValueError: No value for uninitialized VarData object x1[0.015505]
pyomo\common\numeric_types.py:380: ValueError
---------------------------------------------- Captured log call ----------------------------------------------
ERROR pyomo.common.numeric_types:numeric_types.py:391 evaluating object as numeric value: x1[0.015505]
(object: <class 'pyomo.core.base.var.VarData'>)
No value for uninitialized VarData object x1[0.015505]
ERROR pyomo.common.numeric_types:numeric_types.py:391 evaluating object as numeric value: obj
(object: <class 'pyomo.core.base.objective.ScalarObjective'>)
No value for uninitialized VarData object x1[0.015505]
=========================================== short test summary info ===========================================
FAILED pyomo/contrib/gdpopt/tests/test_ldsda.py::TestGDPoptLDSDA::test_solve_four_stage_dynamic_model_minimize - ValueError: No value for uninitialized VarData object x1[0.015505]
========================================= 1 failed, 9 passed in 3.90s =========================================Information on your system
Pyomo version: Pyomo 6.10.0.dev0
Python version: CPython 3.12.12
Operating system: Windows 11
How Pyomo was installed (PyPI, conda, source): Git clone, pip install -e
Solver (if applicable): MIP: appsi_highs & NLP: ipopt