Skip to content
Open
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
43 changes: 36 additions & 7 deletions pyomo/contrib/piecewise/transform/nonlinear_to_pwl.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
from pyomo.common.dependencies import numpy as np, packaging
from pyomo.common.enums import IntEnum
from pyomo.common.modeling import unique_component_name
from pyomo.core.expr.numeric_expr import SumExpression
from pyomo.core.expr.numeric_expr import SumExpression, mutable_expression
from pyomo.core.expr import identify_variables
from pyomo.core.expr import SumExpression
from pyomo.core.util import target_list
Expand Down Expand Up @@ -648,8 +648,9 @@ def _get_bounds_list(self, var_list, obj):
bounds.append((v.bounds, v.is_integer()))
return bounds

def _needs_approximating(self, expr, approximate_quadratic):
repn = self._quadratic_repn_visitor.walk_expression(expr)
def _needs_approximating(self, expr, approximate_quadratic, repn=None):
if repn is None:
repn = self._quadratic_repn_visitor.walk_expression(expr)
if repn.nonlinear is None:
if repn.quadratic is None:
# Linear constraint. Always skip.
Expand All @@ -661,23 +662,51 @@ def _needs_approximating(self, expr, approximate_quadratic):
return ExprType.QUADRATIC, True
return ExprType.GENERAL, True

def _separate_linear_parts(self, repn):
var_map = self._quadratic_repn_visitor.var_map
linear = 0
nonlinear = 0
if repn.nonlinear is not None:
nonlinear += repn.nonlinear
if repn.quadratic:
for (x1, x2), coef in repn.quadratic.items():
if repn.multiplier_flag(coef):
if x1 == x2:
nonlinear += coef * var_map[x1] ** 2
else:
nonlinear += coef * (var_map[x1] * var_map[x2])
Comment on lines +674 to +677
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does separating these two cases matter? I mean, obviously you make a different expression tree, but do we need it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it matters to me.

if repn.linear:
for vid, coef in repn.linear.items():
if repn.multiplier_flag(coef):
linear += coef * var_map[vid]
if repn.constant_flag(repn.constant):
linear += repn.constant
if repn.multiplier_flag(repn.multiplier) != 1:
linear *= repn.multiplier
nonlinear *= repn.multiplier
Comment on lines +684 to +686
Copy link
Contributor

@emma58 emma58 Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jsiirola, can this happen? I have something in the back of my mind telling me multiplier is sure to be 1 at this point?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just copied the to_expression code from the QuadraticRepn class and modified it slightly...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which is to say that I have no idea. I'll take a look, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I can't read. This question was for @jsiirola. My bad.


return linear, nonlinear

def _approximate_expression(
self, expr, obj, trans_block, config, approximate_quadratic
):
repn = self._quadratic_repn_visitor.walk_expression(expr)
expr_type, needs_approximating = self._needs_approximating(
expr, approximate_quadratic
expr, approximate_quadratic, repn
)
if not needs_approximating:
return None, expr_type

linear_part, nonlinear_part = self._separate_linear_parts(repn)

# Additively decompose expr and work on the pieces
pwl_summands = []
pwl_summands = [linear_part]
for k, subexpr in enumerate(
_additively_decompose_expr(
expr, config.min_dimension_to_additively_decompose
nonlinear_part, config.min_dimension_to_additively_decompose
)
if config.additively_decompose
else (expr,)
else (nonlinear_part,)
):
# First check if this is a good idea
expr_vars = list(identify_variables(subexpr, include_fixed=False))
Expand Down
Loading