Skip to content

Commit

Permalink
Adding a config option to only create a single binary for two-term Di…
Browse files Browse the repository at this point in the history
…sjunctions, and adapting hull transformation to at least not be broken by the change
  • Loading branch information
emma58 committed Jul 18, 2024
1 parent 25d5910 commit 54ac75a
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 26 deletions.
37 changes: 37 additions & 0 deletions pyomo/gdp/plugins/gdp_to_mip_transformation.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@

from pyomo.common.autoslots import AutoSlots
from pyomo.common.collections import ComponentMap, DefaultComponentMap
from pyomo.common.config import ConfigDict, ConfigValue
from pyomo.common.log import is_debug_set
from pyomo.common.modeling import unique_component_name

from pyomo.core.base import Transformation, TransformationFactory
from pyomo.core.base.external import ExternalFunction
from pyomo.core.util import target_list
from pyomo.core import (
Any,
Block,
Expand Down Expand Up @@ -64,6 +66,34 @@ class GDP_to_MIP_Transformation(Transformation):
"""
Base class for transformations from GDP to MIP
"""
CONFIG = ConfigDict()
CONFIG.declare(
'targets',
ConfigValue(
default=None,
domain=target_list,
description="target or list of targets that will be relaxed",
doc="""
This specifies the target or list of targets to relax as either a
component or a list of components. If None (default), the entire model
is transformed. Note that if the transformation is done out of place,
the list of targets should be attached to the model before it is cloned,
and the list will specify the targets on the cloned instance.""",
),
)
CONFIG.declare(
'one_indicator_for_two_term',
ConfigValue(
default=False,
domain=bool,
description="Whether or not to reduce to one indicator binary variable "
"in the transformed model for two-term Disjunctions",
doc="""
TODO
"""
),
)

def __init__(self, logger):
"""Initialize transformation object."""
Expand Down Expand Up @@ -102,10 +132,12 @@ def __init__(self, logger):
self._generate_debug_messages = False
self._transformation_blocks = {}
self._algebraic_constraints = {}
self._indicator_var_expr = {}

def _restore_state(self):
self._transformation_blocks.clear()
self._algebraic_constraints.clear()
self._indicator_var_expr = {}
if hasattr(self, '_config'):
del self._config

Expand All @@ -121,6 +153,11 @@ def _process_arguments(self, instance, **kwds):
self._config.set_value(kwds)
self._generate_debug_messages = is_debug_set(self.logger)

def _get_indicator_var(self, disjunct):
if disjunct in self._indicator_var_expr:
return self._indicator_var_expr[disjunct]
return disjunct.binary_indicator_var

def _transform_logical_constraints(self, instance, targets):
# transform any logical constraints that might be anywhere on the stuff
# we're about to transform. We do this before we preprocess targets
Expand Down
43 changes: 17 additions & 26 deletions pyomo/gdp/plugins/hull.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@
is_child_of,
_warn_for_active_disjunct,
)
from pyomo.core.util import target_list
from pyomo.util.vars_from_expressions import get_vars_from_components
from weakref import ref as weakref_ref

Expand Down Expand Up @@ -108,22 +107,7 @@ class Hull_Reformulation(GDP_to_MIP_Transformation):
corresponding OR or XOR constraint.
"""

CONFIG = cfg.ConfigDict('gdp.hull')
CONFIG.declare(
'targets',
cfg.ConfigValue(
default=None,
domain=target_list,
description="target or list of targets that will be relaxed",
doc="""
This specifies the target or list of targets to relax as either a
component or a list of components. If None (default), the entire model
is transformed. Note that if the transformation is done out of place,
the list of targets should be attached to the model before it is cloned,
and the list will specify the targets on the cloned instance.""",
),
)
CONFIG = GDP_to_MIP_Transformation.CONFIG()
CONFIG.declare(
'perspective function',
cfg.ConfigValue(
Expand Down Expand Up @@ -309,6 +293,12 @@ def _transform_disjunctionData(
# change their active status when we transform them, but we still need
# this list after the fact.
active_disjuncts = [disj for disj in obj.disjuncts if disj.active]
two_term = False
if self._config.one_indicator_for_two_term and len(active_disjuncts) == 2:
two_term = True
binary = active_disjuncts[0].binary_indicator_var
self._indicator_var_expr[active_disjuncts[0]] = binary
self._indicator_var_expr[active_disjuncts[1]] = 1 - binary

# We put *all* transformed things on the parent Block of this
# disjunction. We'll mark the disaggregated Vars as local, but beyond
Expand Down Expand Up @@ -405,7 +395,7 @@ def _transform_disjunctionData(
parent_local_var_list = self._get_local_var_list(parent_disjunct)
or_expr = 0
for disjunct in obj.disjuncts:
or_expr += disjunct.indicator_var.get_associated_binary()
or_expr += self._get_indicator_var(disjunct)
if disjunct.active:
self._transform_disjunct(
obj=disjunct,
Expand All @@ -416,10 +406,11 @@ def _transform_disjunctionData(
parent_disjunct_local_vars=local_vars_by_disjunct[parent_disjunct],
disjunct_disaggregated_var_map=disjunct_disaggregated_var_map,
)
xorConstraint.add(index, (or_expr, 1))
# map the DisjunctionData to its XOR constraint to mark it as
# transformed
obj._algebraic_constraint = weakref_ref(xorConstraint[index])
if not two_term:
xorConstraint.add(index, (or_expr, 1))
# map the DisjunctionData to its XOR constraint to mark it as
# transformed
obj._algebraic_constraint = weakref_ref(xorConstraint[index])

# Now add the reaggregation constraints
for var in all_vars_to_disaggregate:
Expand All @@ -439,7 +430,7 @@ def _transform_disjunctionData(
parent_local_var_list.append(disaggregated_var)
local_vars_by_disjunct[parent_disjunct].add(disaggregated_var)
var_free = 1 - sum(
disj.indicator_var.get_associated_binary()
self._get_indicator_var(disj)
for disj in disjuncts_var_appears_in[var]
)
self._declare_disaggregated_var_bounds(
Expand Down Expand Up @@ -546,7 +537,7 @@ def _transform_disjunct(
bigmConstraint=bigmConstraint,
lb_idx='lb',
ub_idx='ub',
var_free_indicator=obj.indicator_var.get_associated_binary(),
var_free_indicator=self._get_indicator_var(obj),
)
# update the bigm constraint mappings
data_dict = disaggregatedVar.parent_block().private_data()
Expand Down Expand Up @@ -575,7 +566,7 @@ def _transform_disjunct(
bigmConstraint=bigmConstraint,
lb_idx='lb',
ub_idx='ub',
var_free_indicator=obj.indicator_var.get_associated_binary(),
var_free_indicator=self._get_indicator_var(obj),
)
# update the bigm constraint mappings
data_dict = var.parent_block().private_data()
Expand Down Expand Up @@ -688,7 +679,7 @@ def _transform_constraint(
c.body, substitute=zero_substitute_map
)

y = disjunct.binary_indicator_var
y = self._get_indicator_var(disjunct)
if NL:
if mode == "LeeGrossmann":
sub_expr = clone_without_expression_components(
Expand Down

0 comments on commit 54ac75a

Please sign in to comment.