From 4036dfa637104bb1a0cd627471df349fe99f7ef2 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 29 Nov 2023 13:27:41 -0700 Subject: [PATCH 1/3] Remove presolve-eliminated variables from named expressions --- pyomo/repn/plugins/nl_writer.py | 9 ++++ pyomo/repn/tests/ampl/test_nlv2.py | 79 ++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 1941e1e0c64..fa706337035 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -1685,6 +1685,15 @@ def _linear_presolve(self, comp_by_linear_var, lcon_by_linear_nnz, var_bounds): if not self.config.linear_presolve: return eliminated_cons, eliminated_vars + # We need to record all named expressions with linear components + # so that any eliminated variables are removed from them. + for expr, info, _ in self.subexpression_cache.values(): + if not info.linear: + continue + expr_id = id(expr) + for _id in info.linear: + comp_by_linear_var[_id].append((expr_id, info)) + fixed_vars = [ _id for _id, (lb, ub) in var_bounds.items() if lb == ub and lb is not None ] diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py index 7e47de24f29..71877e0b6c6 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -1557,6 +1557,85 @@ def test_presolve_lower_triangular_out_of_bounds(self): nlinfo = nl_writer.NLWriter().write(m, OUT, linear_presolve=True) self.assertEqual(LOG.getvalue(), "") + def test_presolve_named_expressions(self): + # Test from #3055 + m = pyo.ConcreteModel() + m.x = pyo.Var([1, 2, 3], initialize=1, bounds=(0, 10)) + m.subexpr = pyo.Expression(pyo.Integers) + m.subexpr[1] = m.x[1] + m.x[2] + m.eq = pyo.Constraint(pyo.Integers) + m.eq[1] = m.x[1] == 7 + m.eq[2] = m.x[3] == 0.1 * m.subexpr[1] * m.x[2] + m.obj = pyo.Objective(expr=m.x[1]**2 + m.x[2]**2 + m.x[3]**3) + + OUT = io.StringIO() + with LoggingIntercept() as LOG: + nlinfo = nl_writer.NLWriter().write(m, OUT, symbolic_solver_labels=True, linear_presolve=True) + self.assertEqual(LOG.getvalue(), "") + + self.assertEqual( + nlinfo.eliminated_vars, + [ + (m.x[1], nl_writer.AMPLRepn(7, {}, None)), + ], + ) + + self.assertEqual( + *nl_diff( + """g3 1 1 0 # problem unknown + 2 1 1 0 1 # vars, constraints, objectives, ranges, eqns + 1 1 0 0 0 0 # nonlinear constrs, objs; ccons: lin, nonlin, nd, nzlb + 0 0 # network constraints: nonlinear, linear + 1 2 1 # nonlinear vars in constraints, objectives, both + 0 0 0 1 # linear network variables; functions; arith, flags + 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) + 2 2 # nonzeros in Jacobian, obj. gradient + 5 4 # max name lengths: constraints, variables + 0 0 0 1 0 # common exprs: b,c,o,c1,o1 +V2 1 1 #subexpr[1] +0 1 +n7.0 +C0 #eq[2] +o16 #- +o2 #* +o2 #* +n0.1 +v2 #subexpr[1] +v0 #x[2] +O0 0 #obj +o54 # sumlist +3 # (n) +o5 #^ +n7.0 +n2 +o5 #^ +v0 #x[2] +n2 +o5 #^ +v1 #x[3] +n3 +x2 # initial guess +0 1 #x[2] +1 1 #x[3] +r #1 ranges (rhs's) +4 0 #eq[2] +b #2 bounds (on variables) +0 0 10 #x[2] +0 0 10 #x[3] +k1 #intermediate Jacobian column lengths +1 +J0 2 #eq[2] +0 0 +1 1 +G0 2 #obj +0 0 +1 0 +""", + OUT.getvalue(), + ) + ) + + def test_scaling(self): m = pyo.ConcreteModel() m.x = pyo.Var(initialize=0) From ae10ad29ad2865ff66ac793623d007c6c9a11f49 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 29 Nov 2023 13:28:11 -0700 Subject: [PATCH 2/3] NFC: remove debugging print() --- pyomo/repn/tests/ampl/test_nlv2.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py index 71877e0b6c6..4bdbef4e31e 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -1480,7 +1480,6 @@ def test_presolve_almost_lower_triangular_nonlinear(self): # Note: bounds on x[1] are: # min(22/3, 82/17, 23/4, -39/-6) == 4.823529411764706 # max(2/3, 62/17, 3/4, -19/-6) == 3.6470588235294117 - print(OUT.getvalue()) self.assertEqual( *nl_diff( """g3 1 1 0 # problem unknown @@ -1743,7 +1742,7 @@ def test_scaling(self): self.assertEqual(LOG.getvalue(), "") nl2 = OUT.getvalue() - print(nl2) + self.assertEqual( *nl_diff( """g3 1 1 0 # problem unknown @@ -1837,7 +1836,7 @@ def test_named_expressions(self): OUT = io.StringIO() nl_writer.NLWriter().write(m, OUT, symbolic_solver_labels=True) - print(OUT.getvalue()) + self.assertEqual( *nl_diff( """g3 1 1 0 # problem unknown From bb5cee6fd6dae6e216c5fea49aef9a8713ab7f98 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 29 Nov 2023 13:38:30 -0700 Subject: [PATCH 3/3] NFC: apply black --- pyomo/repn/tests/ampl/test_nlv2.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py index 4bdbef4e31e..32274f26a0c 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -1565,18 +1565,17 @@ def test_presolve_named_expressions(self): m.eq = pyo.Constraint(pyo.Integers) m.eq[1] = m.x[1] == 7 m.eq[2] = m.x[3] == 0.1 * m.subexpr[1] * m.x[2] - m.obj = pyo.Objective(expr=m.x[1]**2 + m.x[2]**2 + m.x[3]**3) + m.obj = pyo.Objective(expr=m.x[1] ** 2 + m.x[2] ** 2 + m.x[3] ** 3) OUT = io.StringIO() with LoggingIntercept() as LOG: - nlinfo = nl_writer.NLWriter().write(m, OUT, symbolic_solver_labels=True, linear_presolve=True) + nlinfo = nl_writer.NLWriter().write( + m, OUT, symbolic_solver_labels=True, linear_presolve=True + ) self.assertEqual(LOG.getvalue(), "") self.assertEqual( - nlinfo.eliminated_vars, - [ - (m.x[1], nl_writer.AMPLRepn(7, {}, None)), - ], + nlinfo.eliminated_vars, [(m.x[1], nl_writer.AMPLRepn(7, {}, None))] ) self.assertEqual( @@ -1634,7 +1633,6 @@ def test_presolve_named_expressions(self): ) ) - def test_scaling(self): m = pyo.ConcreteModel() m.x = pyo.Var(initialize=0)