Skip to content

Commit 963df83

Browse files
authored
Merge branch 'main' into parmest-doe-doc-reorg
2 parents 40faff7 + 43eac26 commit 963df83

4 files changed

Lines changed: 680 additions & 1 deletion

File tree

pyomo/contrib/piecewise/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
DomainPartitioningMethod,
3636
NonlinearToPWL,
3737
)
38+
from pyomo.contrib.piecewise.transform.factorable import (
39+
UnivariateNonlinearDecompositionTransformation,
40+
)
3841
from pyomo.contrib.piecewise.transform.nested_inner_repn import (
3942
NestedInnerRepresentationGDPTransformation,
4043
)
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
# ____________________________________________________________________________________
2+
#
3+
# Pyomo: Python Optimization Modeling Objects
4+
# Copyright (c) 2008-2026 National Technology and Engineering Solutions of Sandia, LLC
5+
# Under the terms of Contract DE-NA0003525 with National Technology and Engineering
6+
# Solutions of Sandia, LLC, the U.S. Government retains certain rights in this
7+
# software. This software is distributed under the 3-clause BSD License.
8+
# ____________________________________________________________________________________
9+
10+
from pyomo.common.unittest import TestCase, skipUnless
11+
import pyomo.environ as pyo
12+
from pyomo.contrib import piecewise
13+
from pyomo.core.expr.compare import assertExpressionsEqual
14+
from pyomo.common.dependencies import numpy_available, numpy
15+
from pyomo.core.expr.numeric_expr import ProductExpression
16+
17+
18+
def _get_trans():
19+
return pyo.TransformationFactory(
20+
'contrib.piecewise.univariate_nonlinear_decomposition'
21+
)
22+
23+
24+
class TestUnivariateNonlinearDecomposition(TestCase):
25+
def test_multiterm(self):
26+
m = pyo.ConcreteModel()
27+
m.x = pyo.Var()
28+
m.y = pyo.Var()
29+
m.z = pyo.Var()
30+
m.c = pyo.Constraint(expr=m.x + pyo.log(m.y + m.z) + 1 / pyo.exp(m.x**0.5) <= 0)
31+
32+
trans = _get_trans()
33+
trans.apply_to(m)
34+
aux = m.auxiliary
35+
36+
assertExpressionsEqual(self, m.c.body, m.x + aux.x[3] + aux.x[2])
37+
assertExpressionsEqual(self, aux.c[1].expr, aux.x[1] == (m.y + m.z))
38+
assertExpressionsEqual(self, aux.c[2].expr, aux.x[2] == 1 / pyo.exp(m.x**0.5))
39+
assertExpressionsEqual(self, aux.c[3].expr, aux.x[3] == pyo.log(aux.x[1]))
40+
self.assertEqual(m.x.lb, 0)
41+
self.assertIsNone(m.x.ub)
42+
self.assertIsNone(m.y.lb)
43+
self.assertIsNone(m.y.ub)
44+
self.assertIsNone(m.z.lb)
45+
self.assertIsNone(m.z.ub)
46+
self.assertTrue(aux.x[1].lb is None or aux.x[1].lb <= 0)
47+
self.assertIsNone(aux.x[1].ub)
48+
self.assertEqual(aux.x[2].lb, 0)
49+
self.assertEqual(aux.x[2].ub, 1)
50+
self.assertIsNone(aux.x[3].lb)
51+
self.assertIsNone(aux.x[3].ub)
52+
53+
def test_common_subexpressions(self):
54+
m = pyo.ConcreteModel()
55+
m.x = pyo.Var()
56+
m.y = pyo.Var()
57+
m.z1 = pyo.Var()
58+
m.z2 = pyo.Var()
59+
e = -pyo.log(m.x + m.y)
60+
m.c1 = pyo.Constraint(expr=m.z1 + e == 0)
61+
m.c2 = pyo.Constraint(expr=m.z2 + e == 0)
62+
63+
trans = _get_trans()
64+
trans.apply_to(m)
65+
aux = m.auxiliary
66+
67+
assertExpressionsEqual(self, m.c1.expr, m.z1 + aux.x[2] == 0)
68+
assertExpressionsEqual(self, m.c2.expr, m.z2 + aux.x[2] == 0)
69+
assertExpressionsEqual(self, aux.c[1].expr, aux.x[1] == m.x + m.y)
70+
assertExpressionsEqual(self, aux.c[2].expr, aux.x[2] == -pyo.log(aux.x[1]))
71+
72+
def test_product_fixed_variable(self):
73+
m = pyo.ConcreteModel()
74+
m.x = pyo.Var()
75+
m.y = pyo.Var()
76+
m.z = pyo.Var()
77+
m.c = pyo.Constraint(expr=2 * pyo.log(m.x + m.y) <= 0)
78+
79+
trans = _get_trans()
80+
trans.apply_to(m)
81+
aux = m.auxiliary
82+
83+
assertExpressionsEqual(self, m.c.expr, 2 * pyo.log(aux.x[1]) <= 0)
84+
assertExpressionsEqual(self, aux.c[1].expr, aux.x[1] == m.x + m.y)
85+
86+
def test_product_variable_fixed(self):
87+
m = pyo.ConcreteModel()
88+
m.x = pyo.Var()
89+
m.y = pyo.Var()
90+
m.z = pyo.Var()
91+
m.c = pyo.Constraint(expr=pyo.log(m.x + m.y) * 2 <= 0)
92+
93+
trans = _get_trans()
94+
trans.apply_to(m)
95+
aux = m.auxiliary
96+
97+
assertExpressionsEqual(self, m.c.expr, pyo.log(aux.x[1]) * 2 <= 0)
98+
assertExpressionsEqual(self, aux.c[1].expr, aux.x[1] == m.x + m.y)
99+
100+
def test_prod_sum_sum(self):
101+
m = pyo.ConcreteModel()
102+
m.x1 = pyo.Var()
103+
m.x2 = pyo.Var()
104+
m.x3 = pyo.Var()
105+
m.x4 = pyo.Var()
106+
m.c = pyo.Constraint(expr=(m.x1 + m.x2) * (m.x3 + m.x4) <= 1)
107+
108+
trans = _get_trans()
109+
trans.apply_to(m)
110+
aux = m.auxiliary
111+
112+
assertExpressionsEqual(self, m.c.expr, aux.x[1] * aux.x[2] <= 1)
113+
assertExpressionsEqual(self, aux.c[1].expr, aux.x[1] == m.x1 + m.x2)
114+
assertExpressionsEqual(self, aux.c[2].expr, aux.x[2] == m.x3 + m.x4)
115+
116+
def test_pow_sum_sum(self):
117+
m = pyo.ConcreteModel()
118+
m.x1 = pyo.Var()
119+
m.x2 = pyo.Var()
120+
m.x3 = pyo.Var()
121+
m.x4 = pyo.Var()
122+
m.c = pyo.Constraint(expr=(m.x1 + m.x2) ** (m.x3 + m.x4) <= 1)
123+
124+
trans = _get_trans()
125+
trans.apply_to(m)
126+
aux = m.auxiliary
127+
128+
assertExpressionsEqual(self, m.c.expr, aux.x[1] ** aux.x[2] <= 1)
129+
assertExpressionsEqual(self, aux.c[1].expr, aux.x[1] == m.x1 + m.x2)
130+
assertExpressionsEqual(self, aux.c[2].expr, aux.x[2] == m.x3 + m.x4)
131+
132+
def test_division_var_const(self):
133+
m = pyo.ConcreteModel()
134+
m.x = pyo.Var()
135+
m.y = pyo.Var()
136+
m.c = pyo.Constraint(expr=(m.x + m.y) / 2 <= 0)
137+
138+
trans = _get_trans()
139+
trans.apply_to(m)
140+
aux = m.auxiliary
141+
142+
assertExpressionsEqual(self, m.c.expr, (m.x + m.y) / 2 <= 0)
143+
144+
def test_division_sum_sum(self):
145+
m = pyo.ConcreteModel()
146+
m.x1 = pyo.Var()
147+
m.x2 = pyo.Var()
148+
m.x3 = pyo.Var()
149+
m.x4 = pyo.Var()
150+
m.c = pyo.Constraint(expr=(m.x1 + m.x2) / (m.x3 + m.x4) <= 1)
151+
152+
trans = _get_trans()
153+
trans.apply_to(m)
154+
aux = m.auxiliary
155+
156+
assertExpressionsEqual(self, m.c.expr, aux.x[1] * aux.x[3] <= 1)
157+
assertExpressionsEqual(self, aux.c[1].expr, aux.x[1] == m.x1 + m.x2)
158+
assertExpressionsEqual(self, aux.c[2].expr, aux.x[2] == m.x3 + m.x4)
159+
assertExpressionsEqual(self, aux.c[3].expr, aux.x[3] * aux.x[2] == 1)
160+
161+
@skipUnless(numpy_available, "Numpy is not available")
162+
def test_numpy_float(self):
163+
m = pyo.ConcreteModel()
164+
m.x = pyo.Var()
165+
m.y = pyo.Var()
166+
m.z = pyo.Var()
167+
m.c = pyo.Constraint(
168+
expr=ProductExpression((numpy.float64(2.5), pyo.log(m.x + m.y))) <= 0
169+
)
170+
171+
trans = _get_trans()
172+
trans.apply_to(m)
173+
aux = m.auxiliary
174+
175+
assertExpressionsEqual(self, m.c.expr, 2.5 * pyo.log(aux.x[1]) <= 0)
176+
assertExpressionsEqual(self, aux.c[1].expr, aux.x[1] == m.x + m.y)

0 commit comments

Comments
 (0)