Source code for pyomo.gdp.basic_step

# ____________________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright (c) 2008-2026 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 copy
import itertools

from pyomo.core import Block, ConstraintList, Set, Constraint
from pyomo.core.base import Reference
from pyomo.common.modeling import unique_component_name
from pyomo.gdp.disjunct import Disjunct, Disjunction

import logging

logger = logging.getLogger('pyomo.gdp')


def _clone_all_but_indicator_vars(self):
    """Clone everything in a Disjunct except for the indicator_vars"""
    return self.clone(
        {
            id(self.indicator_var): self.indicator_var,
            id(self.binary_indicator_var): self.binary_indicator_var,
        }
    )


def _squish_singletons(tuple_iter):
    """Squish all singleton tuples into their non-tuple values."""
    for tup in tuple_iter:
        if len(tup) == 1:
            yield tup[0]
        else:
            yield tup


[docs] def apply_basic_step(disjunctions_or_constraints): # # Basic steps only apply to XOR'd disjunctions # disjunctions = list( obj for obj in disjunctions_or_constraints if obj.ctype is Disjunction ) constraints = list( obj for obj in disjunctions_or_constraints if obj.ctype is Constraint ) if len(disjunctions) + len(constraints) != len(disjunctions_or_constraints): raise ValueError( 'apply_basic_step only accepts a list containing ' 'Disjunctions or Constraints' ) if not disjunctions: raise ValueError( 'apply_basic_step: argument list must contain at least one Disjunction' ) for d in disjunctions: if not d.xor: raise ValueError( "Basic steps can only be applied to XOR'd disjunctions\n\t" "(raised by disjunction %s)" % (d.name,) ) if not d.active: logger.warning( "Warning: applying basic step to a previously " "deactivated disjunction (%s)" % (d.name,) ) ans = Block(concrete=True) ans.DISJUNCTIONS = Set(initialize=range(len(disjunctions))) ans.INDEX = Set( dimen=len(disjunctions), initialize=_squish_singletons( itertools.product(*tuple(range(len(d.disjuncts)) for d in disjunctions)) ), ) # # Form the individual disjuncts for the new basic step # ans.disjuncts = Disjunct(ans.INDEX) for idx in ans.INDEX: # # Each source disjunct will be copied (cloned) into its own # subblock # ans.disjuncts[idx].src = Block(ans.DISJUNCTIONS) for i in ans.DISJUNCTIONS: src_disj = disjunctions[i].disjuncts[ idx[i] if isinstance(idx, tuple) else idx ] tmp = _clone_all_but_indicator_vars(src_disj) for k, v in list(tmp.component_map().items()): if v.parent_block() is not tmp: # Skip indicator_var and binary_indicator_var continue tmp.del_component(k) ans.disjuncts[idx].src[i].add_component(k, v) # Copy in the constraints corresponding to the improper disjunctions ans.disjuncts[idx].improper_constraints = ConstraintList() for constr in constraints: if constr.is_indexed(): for j in constr: ans.disjuncts[idx].improper_constraints.add( (constr[j].lower, constr[j].body, constr[j].upper) ) constr[j].deactivate() # need this so that we can take an improper basic step with a # ConstraintData else: ans.disjuncts[idx].improper_constraints.add( (constr.lower, constr.body, constr.upper) ) constr.deactivate() # # Link the new disjunct indicator_var's to the original # indicator_var's. Since only one of the new # ans.indicator_links = ConstraintList() for i in ans.DISJUNCTIONS: for j in range(len(disjunctions[i].disjuncts)): orig_var = disjunctions[i].disjuncts[j].indicator_var orig_binary_var = orig_var.get_associated_binary() ans.indicator_links.add( orig_binary_var == sum( ans.disjuncts[idx].binary_indicator_var for idx in ans.INDEX if (idx[i] if isinstance(idx, tuple) else idx) == j ) ) # and throw on a Reference to original on the block for v in (orig_var, orig_binary_var): name_base = v.getname(fully_qualified=True) ans.add_component(unique_component_name(ans, name_base), Reference(v)) # Form the new disjunction ans.disjunction = Disjunction(expr=[ans.disjuncts[i] for i in ans.INDEX]) # # Deactivate the old disjunctions / disjuncts # for i in ans.DISJUNCTIONS: disjunctions[i].deactivate() for d in disjunctions[i].disjuncts: d._deactivate_without_fixing_indicator() return ans
if __name__ == '__main__': from pyomo.environ import ConcreteModel, Constraint, Var m = ConcreteModel() def _d(d, i): d.x = Var(range(i)) d.silly = Constraint(expr=d.indicator_var == i) m.d = Disjunct([1, 2], rule=_d) def _e(e, i): e.y = Var(range(2, i)) m.e = Disjunct([3, 4, 5], rule=_e) m.dd = Disjunction(expr=[m.d[1], m.d[2]]) m.ee = Disjunction(expr=[m.e[3], m.e[4], m.e[5]]) m.Z = apply_basic_step([m.dd, m.ee]) m.pprint()