Skip to content

Commit 4e4a90f

Browse files
authored
Merge pull request #3673 from jsiirola/nl-scaling-named-expr
NL writer: resolve model scaling bug writing `Expression` objects
2 parents 30691b5 + f633ab5 commit 4e4a90f

2 files changed

Lines changed: 100 additions & 7 deletions

File tree

pyomo/repn/plugins/nl_writer.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,7 @@ def write(self, model):
609609
del suffix_data['scaling_factor']
610610
else:
611611
scaling_factor = _NoScalingFactor()
612+
scaling_cache = None
612613
scale_model = scaling_factor.scale
613614

614615
timer.toc("Collected suffixes", level=logging.DEBUG)
@@ -1360,7 +1361,7 @@ def write(self, model):
13601361
# Note: checking target_expr == 0 is equivalent to
13611362
# testing "(_con_id is not None and _obj_id is not None)
13621363
# or _con_id == 0 or _obj_id == 0"
1363-
self._write_v_line(_id, 0)
1364+
self._write_v_line(_id, 0, scale_model, scaling_cache)
13641365
else:
13651366
if target_expr not in single_use_subexpressions:
13661367
single_use_subexpressions[target_expr] = []
@@ -1395,7 +1396,7 @@ def write(self, model):
13951396
break
13961397
if single_use_subexpressions:
13971398
for _id in single_use_subexpressions.get(id(info[0]), ()):
1398-
self._write_v_line(_id, row_idx + 1)
1399+
self._write_v_line(_id, row_idx + 1, scale_model, scaling_cache)
13991400
ostream.write(f'C{row_idx}{row_comments[row_idx]}\n')
14001401
self._write_nl_expression(info[1], False)
14011402

@@ -1408,7 +1409,9 @@ def write(self, model):
14081409
# Note that "Writing .nl files" (2005) is incorrectly
14091410
# missing the "+ 1" in the description of V lines
14101411
# appearing in only Objectives (bottom of page 9).
1411-
self._write_v_line(_id, n_cons + n_lcons + obj_idx + 1)
1412+
self._write_v_line(
1413+
_id, n_cons + n_lcons + obj_idx + 1, scale_model, scaling_cache
1414+
)
14121415
lbl = row_comments[n_cons + obj_idx]
14131416
sense = 0 if info[0].sense == minimize else 1
14141417
ostream.write(f'O{obj_idx} {sense}{lbl}\n')
@@ -1982,7 +1985,7 @@ def _write_nl_expression(self, repn, include_const):
19821985
else:
19831986
self.ostream.write(self.template.const % 0)
19841987

1985-
def _write_v_line(self, expr_id, k):
1988+
def _write_v_line(self, expr_id, k, scale_model, scaling_cache):
19861989
ostream = self.ostream
19871990
column_order = self.column_order
19881991
info = self.subexpression_cache[expr_id]
@@ -1994,10 +1997,14 @@ def _write_v_line(self, expr_id, k):
19941997
# Do NOT write out 0 coefficients here: doing so fouls up the
19951998
# ASL's logic for calculating derivatives, leading to 'nan' in
19961999
# the Hessian results.
1997-
linear = dict(item for item in info[1].linear.items() if item[1])
2000+
linear = info[1].linear
2001+
linear_ids = list(_id for _id, coef in linear.items() if coef)
2002+
if scale_model:
2003+
for _id in linear_ids:
2004+
linear[_id] /= scaling_cache[_id]
19982005
#
1999-
ostream.write(f'V{self.next_V_line_id} {len(linear)} {k}{lbl}\n')
2000-
for _id in sorted(linear, key=column_order.__getitem__):
2006+
ostream.write(f'V{self.next_V_line_id} {len(linear_ids)} {k}{lbl}\n')
2007+
for _id in sorted(linear_ids, key=column_order.__getitem__):
20012008
ostream.write(f'{column_order[_id]} {linear[_id]!s}\n')
20022009
self._write_nl_expression(info[1], True)
20032010
self.next_V_line_id += 1

pyomo/repn/tests/ampl/test_nlv2.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2117,6 +2117,92 @@ def test_scaling(self):
21172117
# Debugging: this diffs the unscaled & scaled models
21182118
# self.assertEqual(*nl_diff(nl1, nl2))
21192119

2120+
def test_scaling_named_expressions(self):
2121+
m = pyo.ConcreteModel()
2122+
2123+
m.x = pyo.Var(initialize=1 / 2)
2124+
m.y = pyo.Var(initialize=1 / 4)
2125+
2126+
m.lin_expr = pyo.Expression(expr=m.x + m.y + m.x**3 + m.y**5)
2127+
m.eqn1 = pyo.Constraint(expr=m.lin_expr**2 == 2)
2128+
m.eqn2 = pyo.Constraint(expr=m.x**2 + m.y == 3)
2129+
2130+
m.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT)
2131+
m.scaling_factor[m.x] = 2
2132+
m.scaling_factor[m.y] = 4
2133+
m.scaling_factor[m.eqn1] = 0.5
2134+
2135+
OUT = io.StringIO()
2136+
with LoggingIntercept() as LOG:
2137+
nlinfo = nl_writer.NLWriter().write(
2138+
m, OUT, scale_model=True, linear_presolve=False
2139+
)
2140+
self.assertEqual(LOG.getvalue(), "")
2141+
2142+
nl2 = OUT.getvalue()
2143+
2144+
self.assertEqual(
2145+
*nl_diff(
2146+
"""g3 1 1 0 #problem unknown
2147+
2 2 0 0 2 #vars, constraints, objectives, ranges, eqns
2148+
2 0 0 0 0 0 #nonlinear constrs, objs; ccons: lin, nonlin, nd, nzlb
2149+
0 0 #network constraints: nonlinear, linear
2150+
2 0 0 #nonlinear vars in constraints, objectives, both
2151+
0 0 0 1 #linear network variables; functions; arith, flags
2152+
0 0 0 0 0 #discrete variables: binary, integer, nonlinear (b,c,o)
2153+
4 0 #nonzeros in Jacobian, obj. gradient
2154+
0 0 #max name lengths: constraints, variables
2155+
0 0 0 2 0 #common exprs: b,c,o,c1,o1
2156+
V2 0 1
2157+
o0
2158+
o5
2159+
o3
2160+
v0
2161+
n2
2162+
n3
2163+
o5
2164+
o3
2165+
v1
2166+
n4
2167+
n5
2168+
V3 2 1
2169+
0 0.5
2170+
1 0.25
2171+
v2
2172+
C0
2173+
o2
2174+
n0.5
2175+
o5
2176+
v3
2177+
n2
2178+
C1
2179+
o5
2180+
o3
2181+
v0
2182+
n2
2183+
n2
2184+
x2
2185+
0 1
2186+
1 1
2187+
r
2188+
4 1
2189+
4 3
2190+
b
2191+
3
2192+
3
2193+
k1
2194+
2
2195+
J0 2
2196+
0 0
2197+
1 0
2198+
J1 2
2199+
0 0
2200+
1 0.25
2201+
""",
2202+
nl2,
2203+
)
2204+
)
2205+
21202206
def test_named_expressions(self):
21212207
# This tests an error possibly reported by #2810
21222208
m = ConcreteModel()

0 commit comments

Comments
 (0)