Source code for pyomo.contrib.gdpopt.GDPopt

# -*- coding: utf-8 -*-
"""Main driver module for GDPopt solver.

20.2.28 changes:
- bugfixes on tests
20.1.22 changes:
- improved subsolver time limit support for GAMS interface
- add maxTimeLimit exit condition for GDPopt-LBB
- add token Big M for reactivated constraints in GDPopt-LBB
- activate fbbt for branch-and-bound nodes
20.1.15 changes:
- internal cleanup of codebase
- merge GDPbb capabilities (logic-based branch and bound)
- refactoring of GDPbb code
- update logging information to include subsolver options
- improve SuppressInfeasibleWarning
- simplify mip preprocessing
- remove not-fully-implemented 'backtracking' from LOA
19.10.11 changes:
- bugfix on SolverStatus error message
19.5.13 changes:
- add handling to integer cuts for disjunct pruning during FBBT
19.4.23 changes:
- add support for linear subproblems
- use automatic differentiation for large constraints
- bugfixes on time limit support
- treat fixed variables as constants in GLOA cut generation
19.3.25 changes:
- add rudimentary time limit support
- start keeping basic changelog

"""
from __future__ import division

from io import StringIO

from pyomo.common.config import (
    add_docstring_list
)
from pyomo.contrib.gdpopt.branch_and_bound import _perform_branch_and_bound
from pyomo.contrib.gdpopt.config_options import _get_GDPopt_config
from pyomo.contrib.gdpopt.iterate import GDPopt_iteration_loop
from pyomo.contrib.gdpopt.master_initialize import (
    GDPopt_initialize_master
)
from pyomo.contrib.gdpopt.util import (
    presolve_lp_nlp, process_objective,
    time_code, indent,
    setup_solver_environment)
from pyomo.opt.base import SolverFactory

__version__ = (20, 2, 28)  # Note: date-based version number


[docs]@SolverFactory.register( 'gdpopt', doc='The GDPopt decomposition-based ' 'Generalized Disjunctive Programming (GDP) solver') class GDPoptSolver(object): """Decomposition solver for Generalized Disjunctive Programming (GDP) problems. The GDPopt (Generalized Disjunctive Programming optimizer) solver applies a variety of decomposition-based approaches to solve Generalized Disjunctive Programming (GDP) problems. GDP models can include nonlinear, continuous variables and constraints, as well as logical conditions. These approaches include: - Logic-based outer approximation (LOA) - Logic-based branch-and-bound (LBB) - Partial surrogate cuts [pending] - Generalized Bender decomposition [pending] This solver implementation was developed by Carnegie Mellon University in the research group of Ignacio Grossmann. For nonconvex problems, LOA may not report rigorous lower/upper bounds. Questions: Please make a post at StackOverflow and/or contact Qi Chen <https://github.com/qtothec> or David Bernal <https://github.com/bernalde>. Several key GDPopt components were prototyped by BS and MS students: - Logic-based branch and bound: Sunjeev Kale - MC++ interface: Johnny Bates - LOA set-covering initialization: Eloy Fernandez - Logic-to-linear transformation: Romeo Valentin """ # Declare configuration options for the GDPopt solver CONFIG = _get_GDPopt_config()
[docs] def solve(self, model, **kwds): """Solve the model. Warning: this solver is still in beta. Keyword arguments subject to change. Undocumented keyword arguments definitely subject to change. This function performs all of the GDPopt solver setup and problem validation. It then calls upon helper functions to construct the initial master approximation and iteration loop. Args: model (Block): a Pyomo model or block to be solved """ config = self.CONFIG(kwds.pop('options', {}), preserve_implicit=True) config.set_value(kwds) if config.strategy is None: msg = 'Please specify solution strategy. Options are: \n' msg += ' LOA: Logic-based Outer Approximation\n' msg += ' GLOA: Global Logic-based Outer Approximation\n' msg += ' LBB: Logic-based Branch and Bound\n' msg += ' RIC: Relaxation with Integer Cuts' raise ValueError(msg) with setup_solver_environment(model, config) as solve_data: self._log_solver_intro_message(config) solve_data.results.solver.name = 'GDPopt %s - %s' % ( str(self.version()), config.strategy) # Verify that objective has correct form process_objective(solve_data, config) # Presolve LP or NLP problems using subsolvers presolved, presolve_results = presolve_lp_nlp(solve_data, config) if presolved: # TODO merge the solver results return presolve_results # problem presolved if solve_data.active_strategy in {'LOA', 'GLOA', 'RIC'}: # Initialize the master problem with time_code(solve_data.timing, 'initialization'): GDPopt_initialize_master(solve_data, config) # Algorithm main loop with time_code(solve_data.timing, 'main loop'): GDPopt_iteration_loop(solve_data, config) elif solve_data.active_strategy == 'LBB': _perform_branch_and_bound(solve_data) else: raise ValueError('Unrecognized strategy: ' + config.strategy) return solve_data.results
"""Support use as a context manager under current solver API""" def __enter__(self): return self def __exit__(self, t, v, traceback): pass
[docs] def available(self, exception_flag=True): """Check if solver is available. TODO: For now, it is always available. However, sub-solvers may not always be available, and so this should reflect that possibility. """ return True
def license_is_valid(self): return True
[docs] def version(self): """Return a 3-tuple describing the solver version.""" return __version__
def _log_solver_intro_message(self, config): config.logger.info( "Starting GDPopt version %s using %s algorithm" % (".".join(map(str, self.version())), config.strategy) ) mip_args_output = StringIO() nlp_args_output = StringIO() minlp_args_output = StringIO() lminlp_args_output = StringIO() config.mip_solver_args.display(ostream=mip_args_output) config.nlp_solver_args.display(ostream=nlp_args_output) config.minlp_solver_args.display(ostream=minlp_args_output) config.local_minlp_solver_args.display(ostream=lminlp_args_output) mip_args_text = indent(mip_args_output.getvalue().rstrip(), prefix=" " * 2 + " - ") nlp_args_text = indent(nlp_args_output.getvalue().rstrip(), prefix=" " * 2 + " - ") minlp_args_text = indent(minlp_args_output.getvalue().rstrip(), prefix=" " * 2 + " - ") lminlp_args_text = indent(lminlp_args_output.getvalue().rstrip(), prefix=" " * 2 + " - ") mip_args_text = "" if len(mip_args_text.strip()) == 0 else \ "\n" + mip_args_text nlp_args_text = "" if len(nlp_args_text.strip()) == 0 else \ "\n" + nlp_args_text minlp_args_text = "" if len(minlp_args_text.strip()) == 0 else \ "\n" + minlp_args_text lminlp_args_text = "" if len(lminlp_args_text.strip()) == 0 else \ "\n" + lminlp_args_text config.logger.info( """ Subsolvers: - MILP: {milp}{milp_args} - NLP: {nlp}{nlp_args} - MINLP: {minlp}{minlp_args} - local MINLP: {lminlp}{lminlp_args} """.format( milp=config.mip_solver, milp_args=mip_args_text, nlp=config.nlp_solver, nlp_args=nlp_args_text, minlp=config.minlp_solver, minlp_args=minlp_args_text, lminlp=config.local_minlp_solver, lminlp_args=lminlp_args_text, ).strip() ) to_cite_text = """ If you use this software, you may cite the following: - Implementation: Chen, Q; Johnson, ES; Bernal, DE; Valentin, R; Kale, S; Bates, J; Siirola, JD; Grossmann, IE. Pyomo.GDP: an ecosystem for logic based modeling and optimization development. Optimization and Engineering, 2021. """.strip() if config.strategy == "LOA": to_cite_text += "\n" to_cite_text += """ - LOA algorithm: Türkay, M; Grossmann, IE. Logic-based MINLP algorithms for the optimal synthesis of process networks. Comp. and Chem. Eng. 1996, 20(8), 959–978. DOI: 10.1016/0098-1354(95)00219-7. """.strip() elif config.strategy == "GLOA": to_cite_text += "\n" to_cite_text += """ - GLOA algorithm: Lee, S; Grossmann, IE. A Global Optimization Algorithm for Nonconvex Generalized Disjunctive Programming and Applications to Process Systems. Comp. and Chem. Eng. 2001, 25, 1675-1697. DOI: 10.1016/S0098-1354(01)00732-3. """.strip() elif config.strategy == "LBB": to_cite_text += "\n" to_cite_text += """ - LBB algorithm: Lee, S; Grossmann, IE. New algorithms for nonlinear generalized disjunctive programming. Comp. and Chem. Eng. 2000, 24, 2125-2141. DOI: 10.1016/S0098-1354(00)00581-0. """.strip() config.logger.info(to_cite_text) _metasolver = False
# Add the CONFIG arguments to the solve method docstring GDPoptSolver.solve.__doc__ = add_docstring_list( GDPoptSolver.solve.__doc__, GDPoptSolver.CONFIG, indent_by=8)