Source code for pyomo.contrib.gdpopt.cut_generation

#  ___________________________________________________________________________
#
#  Pyomo: Python Optimization Modeling Objects
#  Copyright (c) 2008-2024
#  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.
#  ___________________________________________________________________________

"""This module provides functions for cut generation used across multiple
algorithms."""
from math import fabs
from pyomo.common.collections import ComponentSet
from pyomo.core import TransformationFactory, value, Constraint, Block


def _record_binary_value(var, var_value_is_one, var_value_is_zero, int_tol):
    val = value(var)
    if fabs(val - 1) <= int_tol:
        var_value_is_one.add(var)
    elif fabs(val) <= int_tol:
        var_value_is_zero.add(var)
    else:
        raise ValueError(
            'Binary %s = %s is not 0 or 1 within integer tolerance %s'
            % (var.name, val, int_tol)
        )


[docs] def add_no_good_cut(target_model_util_block, config): """Cut the current integer solution from the target model.""" var_value_is_one = ComponentSet() var_value_is_zero = ComponentSet() for var in target_model_util_block.transformed_boolean_variable_list: _record_binary_value( var, var_value_is_one, var_value_is_zero, config.integer_tolerance ) disjuncts = [] if config.force_subproblem_nlp: # We need to also cut the solutions for the other discrete variables for var in target_model_util_block.discrete_variable_list: # This has possible duplicates with the above, but few enough that # we'll leave it for now. if var.is_binary(): _record_binary_value( var, var_value_is_one, var_value_is_zero, config.integer_tolerance ) else: # It's integer. It still has to be in the no-good cut because # else the algorithm is wrong (We're cutting more than just this # solution), so we're going to do this as a Disjunction for # now. I think this is the *wrong* way to model if you are using # GDPopt, so if it's a little inefficient then... We'll deal # with that if it becomes an issue. val = round(value(var), 0) less = var <= val - 1 more = var >= val + 1 disjuncts.extend([less, more]) # It shouldn't be possible to get here unless there's a solution to be cut. assert (var_value_is_one or var_value_is_zero) or len(disjuncts) == 0 int_cut = ( sum(1 - v for v in var_value_is_one) + sum(v for v in var_value_is_zero) ) >= 1 if len(disjuncts) > 0: idx = len(target_model_util_block.no_good_disjunctions) target_model_util_block.no_good_disjunctions[idx] = [ [disj] for disj in disjuncts ] + [[int_cut]] config.logger.debug( 'Adding no-good disjunction: %s' % _disjunction_to_str(target_model_util_block.no_good_disjunctions[idx]) ) # transform it TransformationFactory(config.discrete_problem_transformation).apply_to( target_model_util_block, targets=[target_model_util_block.no_good_disjunctions[idx]], ) else: config.logger.debug('Adding no-good cut: %s' % int_cut) # Exclude the current binary combination target_model_util_block.no_good_cuts.add(expr=int_cut)
def _disjunction_to_str(disjunction): pretty = [] for disjunct in disjunction.disjuncts: exprs = [] for cons in disjunct.component_data_objects( Constraint, active=True, descend_into=Block ): exprs.append(str(cons.expr)) pretty.append("[%s]" % ", ".join(exprs)) return " v ".join(pretty)