Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

contrib.piecewise: Adding transformation to outer representation GDP #2766

Merged
merged 15 commits into from
Mar 17, 2023
Merged
Show file tree
Hide file tree
Changes from 10 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
5 changes: 5 additions & 0 deletions pyomo/contrib/piecewise/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@
InnerRepresentationGDPTransformation)
from pyomo.contrib.piecewise.transform.disaggregated_convex_combination import (
DisaggregatedConvexCombinationTransformation)
from pyomo.contrib.piecewise.transform.outer_representation_gdp import (
OuterRepresentationGDPTransformation)
from pyomo.contrib.piecewise.transform.multiple_choice import (
MultipleChoiceTransformation)

79 changes: 79 additions & 0 deletions pyomo/contrib/piecewise/tests/common_tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# ___________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright (c) 2008-2022
# National Technology and Engineering Solutions of Sandia, LLC
# Under the terms of Contract DE-NA0003525 with National Technology and
# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain
# rights in this software.
# This software is distributed under the 3-clause BSD License.
# ___________________________________________________________________________

import pyomo.contrib.piecewise.tests.models as models
from pyomo.core import Var
from pyomo.core.base import TransformationFactory
from pyomo.environ import value
from pyomo.gdp import Disjunct, Disjunction

def check_trans_block_structure(test, block):
# One (indexed) disjunct
test.assertEqual(len(block.component_map(Disjunct)), 1)
# One disjunction
test.assertEqual(len(block.component_map(Disjunction)), 1)
# The 'z' var (that we will substitute in for the function being
# approximated) is here:
test.assertEqual(len(block.component_map(Var)), 1)
test.assertIsInstance(block.substitute_var, Var)

def check_log_x_model_soln(test, m):
test.assertAlmostEqual(value(m.x), 4)
test.assertAlmostEqual(value(m.x1), 1)
test.assertAlmostEqual(value(m.x2), 1)
test.assertAlmostEqual(value(m.obj), m.f2(4))

def check_transformation_do_not_descend(test, transformation):
m = models.make_log_x_model()
transform = TransformationFactory(transformation)
transform.apply_to(m)

test.check_pw_log(m)
test.check_pw_paraboloid(m)

def check_transformation_PiecewiseLinearFunction_targets(test, transformation):
m = models.make_log_x_model()
transform = TransformationFactory(transformation)
transform.apply_to(m, targets=[m.pw_log])

test.check_pw_log(m)

# And check that the paraboloid was *not* transformed.
test.assertIsNone(
m.pw_paraboloid.get_transformation_var(m.paraboloid_expr))

def check_descend_into_expressions(test, transformation):
m = models.make_log_x_model()
transform = TransformationFactory(transformation)
transform.apply_to(m, descend_into_expressions=True)

# Everything should be transformed
test.check_pw_log(m)
test.check_pw_paraboloid(m)

def check_descend_into_expressions_constraint_target(test, transformation):
m = models.make_log_x_model()
transform = TransformationFactory(transformation)
transform.apply_to(m, descend_into_expressions=True, targets=[m.indexed_c])

test.check_pw_paraboloid(m)
# And check that the log was *not* transformed.
test.assertIsNone(m.pw_log.get_transformation_var(m.log_expr))

def check_descend_into_expressions_objective_target(test, transformation):
m = models.make_log_x_model()
transform = TransformationFactory(transformation)
transform.apply_to(m, descend_into_expressions=True, targets=[m.obj])

test.check_pw_log(m)
# And check that the paraboloid was *not* transformed.
test.assertIsNone(
m.pw_paraboloid.get_transformation_var(m.paraboloid_expr))
63 changes: 63 additions & 0 deletions pyomo/contrib/piecewise/tests/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# ___________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright (c) 2008-2022
# National Technology and Engineering Solutions of Sandia, LLC
# Under the terms of Contract DE-NA0003525 with National Technology and
# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain
# rights in this software.
# This software is distributed under the 3-clause BSD License.
# ___________________________________________________________________________

from pyomo.contrib.piecewise import PiecewiseLinearFunction
from pyomo.environ import ConcreteModel, Constraint, log, Objective, Var

def make_log_x_model():
m = ConcreteModel()
m.x = Var(bounds=(1, 10))
def log_function(x):
return log(x)
m.log_function = log_function
m.pw_log = PiecewiseLinearFunction(points=[1, 3, 6, 10],
function=m.log_function)
emma58 marked this conversation as resolved.
Show resolved Hide resolved

# Here are the linear functions, for safe keeping.
def f1(x):
return (log(3)/2)*x - log(3)/2
m.f1 = f1
def f2(x):
return (log(2)/3)*x + log(3/2)
m.f2 = f2
def f3(x):
return (log(5/3)/4)*x + log(6/((5/3)**(3/2)))
m.f3 = f3

m.log_expr = m.pw_log(m.x)
m.obj = Objective(expr=m.log_expr)

m.x1 = Var(bounds=(0, 3))
m.x2 = Var(bounds=(1, 7))

## apprximates paraboloid x1**2 + x2**2
def g1(x1, x2):
return 3*x1 + 5*x2 - 4
m.g1 = g1
def g2(x1, x2):
return 3*x1 + 11*x2 - 28
m.g2 = g2
simplices = [[(0, 1), (0, 4), (3, 4)],
[(0, 1), (3, 4), (3, 1)],
[(3, 4), (3, 7), (0, 7)],
[(0, 7), (0, 4), (3, 4)]]
m.pw_paraboloid = PiecewiseLinearFunction(simplices=simplices,
linear_functions=[g1, g1, g2,
g2])
m.paraboloid_expr = m.pw_paraboloid(m.x1, m.x2)
def c_rule(m, i):
if i == 0:
return m.x >= m.paraboloid_expr
else:
return (1, m.x1, 2)
m.indexed_c = Constraint([0, 1], rule=c_rule)

return m
133 changes: 21 additions & 112 deletions pyomo/contrib/piecewise/tests/test_inner_repn_gdp.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,76 +10,15 @@
# ___________________________________________________________________________

import pyomo.common.unittest as unittest
from pyomo.contrib.piecewise import (
PiecewiseLinearFunction, PiecewiseLinearExpression)
from pyomo.contrib.piecewise.tests import models
import pyomo.contrib.piecewise.tests.common_tests as ct
from pyomo.core.base import TransformationFactory
from pyomo.core.expr.compare import (
assertExpressionsEqual, assertExpressionsStructurallyEqual)
from pyomo.gdp import Disjunct, Disjunction
from pyomo.environ import (
ConcreteModel, Constraint, log, SolverFactory, Objective, value, Var)
from pyomo.environ import Constraint, SolverFactory, Var

class TestTransformPiecewiseModelToInnerRepnGDP(unittest.TestCase):
def make_model(self):
m = ConcreteModel()
m.x = Var(bounds=(1, 10))
def log_function(x):
return log(x)
m.log_function = log_function
m.pw_log = PiecewiseLinearFunction(points=[1, 3, 6, 10],
function=m.log_function)

# Here are the linear functions, for safe keeping.
def f1(x):
return (log(3)/2)*x - log(3)/2
m.f1 = f1
def f2(x):
return (log(2)/3)*x + log(3/2)
m.f2 = f2
def f3(x):
return (log(5/3)/4)*x + log(6/((5/3)**(3/2)))
m.f3 = f3

m.log_expr = m.pw_log(m.x)
m.obj = Objective(expr=m.log_expr)

m.x1 = Var(bounds=(0, 3))
m.x2 = Var(bounds=(1, 7))

## apprximates paraboloid x1**2 + x2**2
def g1(x1, x2):
return 3*x1 + 5*x2 - 4
m.g1 = g1
def g2(x1, x2):
return 3*x1 + 11*x2 - 28
m.g2 = g2
simplices = [[(0, 1), (0, 4), (3, 4)],
[(0, 1), (3, 4), (3, 1)],
[(3, 4), (3, 7), (0, 7)],
[(0, 7), (0, 4), (3, 4)]]
m.pw_paraboloid = PiecewiseLinearFunction(simplices=simplices,
linear_functions=[g1, g1, g2,
g2])
m.paraboloid_expr = m.pw_paraboloid(m.x1, m.x2)
def c_rule(m, i):
if i == 0:
return m.x >= m.paraboloid_expr
else:
return (1, m.x1, 2)
m.indexed_c = Constraint([0, 1], rule=c_rule)

return m

def check_trans_block_structure(self, block):
# One (indexed) disjunct
self.assertEqual(len(block.component_map(Disjunct)), 1)
# One disjunction
self.assertEqual(len(block.component_map(Disjunction)), 1)
# The 'z' var (that we will substitute in for the function being
# approximated) is here:
self.assertEqual(len(block.component_map(Var)), 1)
self.assertIsInstance(block.substitute_var, Var)

def check_log_disjunct(self, d, pts, f, substitute_var, x):
self.assertEqual(len(d.component_map(Constraint)), 3)
# lambdas and indicator_var
Expand Down Expand Up @@ -135,7 +74,7 @@ def check_pw_log(self, m):
self.assertIsInstance(z, Var)
# Now we can use those Vars to check on what the transformation created
log_block = z.parent_block()
self.check_trans_block_structure(log_block)
ct.check_trans_block_structure(self, log_block)

# Check that all of the Disjuncts have what they should
self.assertEqual(len(log_block.disjuncts), 3)
Expand Down Expand Up @@ -164,7 +103,7 @@ def check_pw_paraboloid(self, m):
z = m.pw_paraboloid.get_transformation_var(m.paraboloid_expr)
self.assertIsInstance(z, Var)
paraboloid_block = z.parent_block()
self.check_trans_block_structure(paraboloid_block)
ct.check_trans_block_structure(self, paraboloid_block)

self.assertEqual(len(paraboloid_block.disjuncts), 4)
disjuncts_dict = {
Expand All @@ -190,64 +129,34 @@ def check_pw_paraboloid(self, m):
paraboloid_block.substitute_var)

def test_transformation_do_not_descend(self):
m = self.make_model()
inner_repn = TransformationFactory('contrib.piecewise.inner_repn_gdp')
inner_repn.apply_to(m)

self.check_pw_log(m)
self.check_pw_paraboloid(m)
ct.check_transformation_do_not_descend(self,
'contrib.piecewise.inner_repn_gdp')

def test_transformation_PiecewiseLinearFunction_targets(self):
m = self.make_model()
inner_repn = TransformationFactory('contrib.piecewise.inner_repn_gdp')
inner_repn.apply_to(m, targets=[m.pw_log])

self.check_pw_log(m)

# And check that the paraboloid was *not* transformed.
self.assertIsNone(
m.pw_paraboloid.get_transformation_var(m.paraboloid_expr))
ct.check_transformation_PiecewiseLinearFunction_targets(
self,
'contrib.piecewise.inner_repn_gdp')

def test_descend_into_expressions(self):
m = self.make_model()
inner_repn = TransformationFactory('contrib.piecewise.inner_repn_gdp')
inner_repn.apply_to(m, descend_into_expressions=True)

# Everything should be transformed
self.check_pw_log(m)
self.check_pw_paraboloid(m)
ct.check_descend_into_expressions(self,
'contrib.piecewise.inner_repn_gdp')

def test_descend_into_expressions_constraint_target(self):
m = self.make_model()
inner_repn = TransformationFactory('contrib.piecewise.inner_repn_gdp')
inner_repn.apply_to(m, descend_into_expressions=True,
targets=[m.indexed_c])

self.check_pw_paraboloid(m)
# And check that the log was *not* transformed.
self.assertIsNone(m.pw_log.get_transformation_var(m.log_expr))
ct.check_descend_into_expressions_constraint_target(
self,
'contrib.piecewise.inner_repn_gdp')

def test_descend_into_expressions_objective_target(self):
m = self.make_model()
inner_repn = TransformationFactory('contrib.piecewise.inner_repn_gdp')
inner_repn.apply_to(m, descend_into_expressions=True,
targets=[m.obj])

self.check_pw_log(m)
# And check that the paraboloid was *not* transformed.
self.assertIsNone(
m.pw_paraboloid.get_transformation_var(m.paraboloid_expr))
ct.check_descend_into_expressions_objective_target(
self,
'contrib.piecewise.inner_repn_gdp')

@unittest.skipUnless(SolverFactory('gurobi').available(),
'Gurobi is not available')
def test_solve_disaggregated_convex_combo_model(self):
m = self.make_model()
m = models.make_log_x_model()
TransformationFactory(
'contrib.piecewise.disaggregated_convex_combination').apply_to(m)
SolverFactory('gurobi').solve(m)

SolverFactory('gurobi').solve(m, tee=True)

self.assertAlmostEqual(value(m.x), 4)
self.assertAlmostEqual(value(m.x1), 1)
self.assertAlmostEqual(value(m.x2), 1)
self.assertAlmostEqual(value(m.obj), m.f2(4))
ct.check_log_x_model_soln(self, m)
Loading