Repository: Pyomo/pyomo

Issue:
Bug in Ampl file generation
I have a model which can be successfully solved by pyomo 6.4.2

When i upgrade to 6.5.0, it generates the following ampl file ([tmpptnjgdzypynumero.nl.txt](https://github.com/Pyomo/pyomo/files/11250039/tmpptnjgdzypynumero.nl.txt) ) which has problems starting from line 1021

The constraint in question looks something like this: 
```
Member of gain_cons} : Size=72, Index=splitter.gain_cons_index, Active=True
    Key : Lower : Body : Upper : Active
      4 :   0.0      : 100.00000000000001*(- splitter.split_fraction_1_ctx_var_oper[12]) + 0.49999999999999983*(- splitter.inlet_mass_flow_ctx_var_oper[12]) + 100.00000000000001*splitter.split_fraction_1_ctx_var[12] + 0.49999999999999983*splitter.inlet_mass_flow_ctx_var[12] - (splitter.outlet_1_mass_flow_ctx_var[12] - splitter.outlet_1_mass_flow_ctx_var_oper[12]) :   0.0 :   True
```
On line 682 of nl_writer.py the `expr.const` ends up being type `NPV_SumExpression`, which may be the problem





Discussion:
Thank you for reporting your issue.  Unfortunately, without an example that we can use to reproduce the problem, there is little we can do to diagnose what is happening.

Have you confirmed that you see the same behavior with the current development branch?

In the future, please leverage the prepopulated bug template and provide the requested information.  This includes information about your environment, the full error message, and steps to reproduce the observed bug.
Unfortunately i get the same issue also when using the main branch (i am guessing that is the development branch)
Recreating the model on your side is not an option at the moment as it will be too complicated. 

I was hoping you could directly guess from the info that i provided what the bug was. As that is not the case, i would try to isolate the bug.

is there a way to somehow recreate it without  recreating the entire model?
Thanks.
By chance, are you using `LinearExpression` objects or components out of the `kernel` module?
I am not using the kernel

Resolving patch:
diff --git a/pyomo/core/expr/numeric_expr.py b/pyomo/core/expr/numeric_expr.py
--- a/pyomo/core/expr/numeric_expr.py
+++ b/pyomo/core/expr/numeric_expr.py
@@ -933,9 +933,16 @@ def __init__(self, args=None, constant=None, linear_coefs=None, linear_vars=None
                     "Cannot specify both args and any of "
                     "{constant, linear_coeffs, or linear_vars}"
                 )
-            if len(args) > 1 and (
-                args[1].__class__ in native_types
-                or not args[1].is_potentially_variable()
+            if (
+                len(args) % 2 == 1
+                and all(
+                    arg.__class__ in native_types or not arg.is_potentially_variable()
+                    for arg in args[: 1 + len(args) // 2]
+                )
+                and not any(
+                    arg.__class__ in native_types or not arg.is_potentially_variable()
+                    for arg in args[1 + len(args) // 2 :]
+                )
             ):
                 deprecation_warning(
                     "LinearExpression has been updated to expect args= to "
@@ -988,22 +995,17 @@ def _args_(self):
     @_args_.setter
     def _args_(self, value):
         self._args_cache_ = list(value)
+        self.constant = 0
+        self.linear_coefs = []
+        self.linear_vars = []
         if not self._args_cache_:
-            self.constant = 0
-            self.linear_coefs = []
-            self.linear_vars = []
             return
-        if self._args_cache_[0].__class__ is not MonomialTermExpression:
-            self.constant = value[0]
-            first_var = 1
-        else:
-            self.constant = 0
-            first_var = 0
-        self.linear_coefs, self.linear_vars = zip(
-            *map(attrgetter('args'), value[first_var:])
-        )
-        self.linear_coefs = list(self.linear_coefs)
-        self.linear_vars = list(self.linear_vars)
+        for arg in self._args_cache_:
+            if arg.__class__ is not MonomialTermExpression:
+                self.constant += arg
+            else:
+                self.linear_coefs.append(arg.arg(0))
+                self.linear_vars.append(arg.arg(1))
 
     def create_node_with_local_data(self, args, classtype=None):
         if classtype is not None:
diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py
--- a/pyomo/repn/plugins/nl_writer.py
+++ b/pyomo/repn/plugins/nl_writer.py
@@ -73,13 +73,6 @@
 
 from pyomo.repn.plugins.ampl.ampl_ import set_pyomo_amplfunc_env
 
-if sys.version_info[:2] >= (3, 7):
-    _deterministic_dict = dict
-else:
-    from pyomo.common.collections import OrderedDict
-
-    _deterministic_dict = OrderedDict
-
 ### FIXME: Remove the following as soon as non-active components no
 ### longer report active==True
 from pyomo.core.base import Set, RangeSet
@@ -516,7 +509,7 @@ def __init__(self, ostream, rowstream, colstream, config):
         self.subexpression_order = []
         self.external_functions = {}
         self.used_named_expressions = set()
-        self.var_map = _deterministic_dict()
+        self.var_map = {}
         self.visitor = AMPLRepnVisitor(
             self.template,
             self.subexpression_cache,
@@ -2446,6 +2439,8 @@ def _before_linear(visitor, child):
     # the original expression tree.
     var_map = visitor.var_map
     const = child.constant
+    if const.__class__ not in native_types:
+        const = const()
     linear = {}
     for v, c in zip(child.linear_vars, child.linear_coefs):
         if c.__class__ not in native_types:


Test patch:
diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py
--- a/pyomo/repn/tests/ampl/test_nlv2.py
+++ b/pyomo/repn/tests/ampl/test_nlv2.py
@@ -17,10 +17,11 @@
 import os
 
 import pyomo.repn.plugins.nl_writer as nl_writer
+from pyomo.repn.tests.ampl.nl_diff import nl_diff
 
 from pyomo.common.log import LoggingIntercept
 from pyomo.common.tempfiles import TempfileManager
-from pyomo.core.expr.current import Expr_if, inequality
+from pyomo.core.expr.current import Expr_if, inequality, LinearExpression
 from pyomo.core.base.expression import ScalarExpression
 from pyomo.environ import (
     ConcreteModel,
@@ -44,7 +45,7 @@ def __init__(self, symbolic=False):
         self.subexpression_cache = {}
         self.subexpression_order = []
         self.external_functions = {}
-        self.var_map = nl_writer._deterministic_dict()
+        self.var_map = {}
         self.used_named_expressions = set()
         self.symbolic_solver_labels = symbolic
 
@@ -471,6 +472,31 @@ def test_errors_propagate_nan(self):
         self.assertEqual(repn.linear, {})
         self.assertEqual(repn.nonlinear, None)
 
+    def test_linearexpression_npv(self):
+        m = ConcreteModel()
+        m.x = Var(initialize=4)
+        m.y = Var(initialize=4)
+        m.z = Var(initialize=4)
+        m.p = Param(initialize=5, mutable=True)
+
+        info = INFO()
+        with LoggingIntercept() as LOG:
+            repn = info.visitor.walk_expression(
+                (
+                    LinearExpression(
+                        args=[1, m.p, m.p * m.x, (m.p + 2) * m.y, 3 * m.z, m.p * m.z]
+                    ),
+                    None,
+                    None,
+                )
+            )
+        self.assertEqual(LOG.getvalue(), "")
+        self.assertEqual(repn.nl, None)
+        self.assertEqual(repn.mult, 1)
+        self.assertEqual(repn.const, 6)
+        self.assertEqual(repn.linear, {id(m.x): 5, id(m.y): 7, id(m.z): 8})
+        self.assertEqual(repn.nonlinear, None)
+
     def test_eval_pow(self):
         m = ConcreteModel()
         m.x = Var(initialize=4)
@@ -886,3 +912,47 @@ def d(m, i):
             "Skipping.\n",
             LOG.getvalue(),
         )
+
+    def test_linear_constraint_npv_const(self):
+        # This tests an error possibly reported by #2810
+        m = ConcreteModel()
+        m.x = Var([1, 2])
+        m.p = Param(initialize=5, mutable=True)
+        m.o = Objective(expr=1)
+        m.c = Constraint(
+            expr=LinearExpression([m.p**2, 5 * m.x[1], 10 * m.x[2]]) == 0
+        )
+
+        OUT = io.StringIO()
+        nl_writer.NLWriter().write(m, OUT)
+        self.assertEqual(
+            *nl_diff(
+                """g3 1 1 0	# problem unknown
+ 2 1 1 0 1 	# vars, constraints, objectives, ranges, eqns
+ 0 0 0 0 0 0	# nonlinear constrs, objs; ccons: lin, nonlin, nd, nzlb
+ 0 0	# network constraints: nonlinear, linear
+ 0 0 0 	# 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 0 	# nonzeros in Jacobian, obj. gradient
+ 0 0	# max name lengths: constraints, variables
+ 0 0 0 0 0	# common exprs: b,c,o,c1,o1
+C0
+n0
+O0 0
+n1.0
+x0
+r
+4 -25
+b
+3
+3
+k1
+1
+J0 2
+0 5
+1 10
+""",
+                OUT.getvalue(),
+            )
+        )