Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 16 additions & 13 deletions pyomo/repn/linear.py
Original file line number Diff line number Diff line change
Expand Up @@ -657,20 +657,23 @@ def __init__(self):
@staticmethod
def record_monomial(visitor, result, coef, var):
_id = id(var)
if var.fixed:
# Fixed variables contribute constants even if they were
# already registered in the var_map earlier while still
# free (gh-3851)
var = check_constant(var.value, var, visitor)
if not coef and var.__class__ is InvalidNumber:
deprecation_warning(
f"Encountered {coef}*{val2str(var)} in expression "
"tree. Mapping the NaN result to 0 for compatibility "
"with the lp_v1 writer. In the future, this NaN "
"will be preserved/emitted to comply with IEEE-754.",
version='6.6.0',
)
else:
result.constant += coef * var
return
if _id not in visitor.var_map:
if var.fixed:
var = check_constant(var.value, var, visitor)
if not coef and var.__class__ is InvalidNumber:
deprecation_warning(
f"Encountered {coef}*{val2str(var)} in expression "
"tree. Mapping the NaN result to 0 for compatibility "
"with the lp_v1 writer. In the future, this NaN "
"will be preserved/emitted to comply with IEEE-754.",
version='6.6.0',
)
else:
result.constant += coef * var
return
visitor.var_recorder.add(var)
if _id in result.linear:
result.linear[_id] += coef
Expand Down
47 changes: 47 additions & 0 deletions pyomo/repn/tests/test_linear.py
Original file line number Diff line number Diff line change
Expand Up @@ -1799,3 +1799,50 @@ def test_var_order(self):
repn.linear, {id(m.x[0]): 1, id(m.x[1]): 2, id(m.x[2]): 3, id(m.x[3]): 4}
)
self.assertEqual(repn.nonlinear, None)


class TestFixedVarAfterRegistration(unittest.TestCase):
# gh-3851: variables that were registered in the var_map while
# still free must be handled as constants once fixed
def test_fixed_var_after_registration(self):
from pyomo.environ import Set
from pyomo.repn.util import SortComponents, TemplateVarRecorder

m = ConcreteModel()
m.a = Set(initialize=[1, 2, 3])
m.x = Var(m.a)
e1 = m.x[1] * (m.x[2] + m.x[3])
e2 = m.x[1] - 1 / m.x[2]
m.x[2].fix(2)

visitor = LinearRepnVisitor(
subexpression_cache={},
var_recorder=TemplateVarRecorder({}, SortComponents.ORDERED_INDICES),
)
visitor.walk_expression(e1)
repn = visitor.walk_expression(e2)

self.assertIsNone(repn.nonlinear)
self.assertEqual(repn.constant, -0.5)
self.assertEqual(repn.linear, {id(m.x[1]): 1})

def test_fixed_var_substituted_before_registration(self):
from pyomo.environ import Set
from pyomo.repn.util import SortComponents, TemplateVarRecorder

m = ConcreteModel()
m.a = Set(initialize=[1, 2, 3])
m.x = Var(m.a)
e1 = m.x[1] * (m.x[2] + m.x[3])
m.x[2].fix(2)

visitor = LinearRepnVisitor(
subexpression_cache={},
var_recorder=TemplateVarRecorder({}, SortComponents.ORDERED_INDICES),
)
repn = visitor.walk_expression(e1)

# x[2] is fixed, so it never becomes a linear term and its
# value does not appear in the nonlinear remainder
self.assertEqual(repn.linear, {})
self.assertNotIn("x[2]", str(repn.nonlinear))
Loading