Skip to content

Commit cd9d6ce

Browse files
authored
Merge pull request #3193 from jsiirola/nlv2-linear-independent-subsystems
NLv2: handle presolved independent linear subsystems
2 parents 1b5aa97 + b3a4b06 commit cd9d6ce

File tree

2 files changed

+102
-2
lines changed

2 files changed

+102
-2
lines changed

Diff for: pyomo/repn/plugins/nl_writer.py

+30-2
Original file line numberDiff line numberDiff line change
@@ -1081,9 +1081,37 @@ def write(self, model):
10811081

10821082
# Update any eliminated variables to point to the (potentially
10831083
# scaled) substituted variables
1084-
for _id, expr_info in eliminated_vars.items():
1084+
for _id, expr_info in list(eliminated_vars.items()):
10851085
nl, args, _ = expr_info.compile_repn(visitor)
1086-
_vmap[_id] = nl.rstrip() % tuple(_vmap[_id] for _id in args)
1086+
for _i in args:
1087+
# It is possible that the eliminated variable could
1088+
# reference another variable that is no longer part of
1089+
# the model and therefore does not have a _vmap entry.
1090+
# This can happen when there is an underdetermined
1091+
# independent linear subsystem and the presolve removed
1092+
# all the constraints from the subsystem. Because the
1093+
# free variables in the subsystem are not referenced
1094+
# anywhere else in the model, they are not part of the
1095+
# `variables` list. Implicitly "fix" it to an arbitrary
1096+
# valid value from the presolved domain (see #3192).
1097+
if _i not in _vmap:
1098+
lb, ub = var_bounds[_i]
1099+
if lb is None:
1100+
lb = -inf
1101+
if ub is None:
1102+
ub = inf
1103+
if lb <= 0 <= ub:
1104+
val = 0
1105+
else:
1106+
val = lb if abs(lb) < abs(ub) else ub
1107+
eliminated_vars[_i] = AMPLRepn(val, {}, None)
1108+
_vmap[_i] = expr_info.compile_repn(visitor)[0]
1109+
logger.warning(
1110+
"presolve identified an underdetermined independent "
1111+
"linear subsystem that was removed from the model. "
1112+
f"Setting '{var_map[_i]}' == {val}"
1113+
)
1114+
_vmap[_id] = nl.rstrip() % tuple(_vmap[_i] for _i in args)
10871115

10881116
r_lines = [None] * n_cons
10891117
for idx, (con, expr_info, lb, ub) in enumerate(constraints):

Diff for: pyomo/repn/tests/ampl/test_nlv2.py

+72
Original file line numberDiff line numberDiff line change
@@ -1812,6 +1812,78 @@ def test_presolve_zero_coef(self):
18121812
)
18131813
)
18141814

1815+
def test_presolve_independent_subsystem(self):
1816+
# This is derived from the example in #3192
1817+
m = ConcreteModel()
1818+
m.x = Var()
1819+
m.y = Var()
1820+
m.z = Var()
1821+
m.d = Constraint(expr=m.z == m.y)
1822+
m.c = Constraint(expr=m.y == m.x)
1823+
m.o = Objective(expr=0)
1824+
1825+
ref = """g3 1 1 0 #problem unknown
1826+
0 0 1 0 0 #vars, constraints, objectives, ranges, eqns
1827+
0 0 0 0 0 0 #nonlinear constrs, objs; ccons: lin, nonlin, nd, nzlb
1828+
0 0 #network constraints: nonlinear, linear
1829+
0 0 0 #nonlinear vars in constraints, objectives, both
1830+
0 0 0 1 #linear network variables; functions; arith, flags
1831+
0 0 0 0 0 #discrete variables: binary, integer, nonlinear (b,c,o)
1832+
0 0 #nonzeros in Jacobian, obj. gradient
1833+
1 0 #max name lengths: constraints, variables
1834+
0 0 0 0 0 #common exprs: b,c,o,c1,o1
1835+
O0 0 #o
1836+
n0
1837+
x0 #initial guess
1838+
r #0 ranges (rhs's)
1839+
b #0 bounds (on variables)
1840+
k-1 #intermediate Jacobian column lengths
1841+
"""
1842+
1843+
OUT = io.StringIO()
1844+
with LoggingIntercept() as LOG:
1845+
nlinfo = nl_writer.NLWriter().write(
1846+
m, OUT, symbolic_solver_labels=True, linear_presolve=True
1847+
)
1848+
self.assertEqual(
1849+
LOG.getvalue(),
1850+
"presolve identified an underdetermined independent linear subsystem "
1851+
"that was removed from the model. Setting 'z' == 0\n",
1852+
)
1853+
1854+
self.assertEqual(*nl_diff(ref, OUT.getvalue()))
1855+
1856+
m.x.lb = 5.0
1857+
1858+
OUT = io.StringIO()
1859+
with LoggingIntercept() as LOG:
1860+
nlinfo = nl_writer.NLWriter().write(
1861+
m, OUT, symbolic_solver_labels=True, linear_presolve=True
1862+
)
1863+
self.assertEqual(
1864+
LOG.getvalue(),
1865+
"presolve identified an underdetermined independent linear subsystem "
1866+
"that was removed from the model. Setting 'z' == 5.0\n",
1867+
)
1868+
1869+
self.assertEqual(*nl_diff(ref, OUT.getvalue()))
1870+
1871+
m.x.lb = -5.0
1872+
m.z.ub = -2.0
1873+
1874+
OUT = io.StringIO()
1875+
with LoggingIntercept() as LOG:
1876+
nlinfo = nl_writer.NLWriter().write(
1877+
m, OUT, symbolic_solver_labels=True, linear_presolve=True
1878+
)
1879+
self.assertEqual(
1880+
LOG.getvalue(),
1881+
"presolve identified an underdetermined independent linear subsystem "
1882+
"that was removed from the model. Setting 'z' == -2.0\n",
1883+
)
1884+
1885+
self.assertEqual(*nl_diff(ref, OUT.getvalue()))
1886+
18151887
def test_scaling(self):
18161888
m = pyo.ConcreteModel()
18171889
m.x = pyo.Var(initialize=0)

0 commit comments

Comments
 (0)